diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index c71c638..e18927e 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -1,12 +1,14 @@
name: CI
on:
push:
- branches-ignore:
- - 'generated'
- - 'codegen/**'
- - 'integrated/**'
- - 'stl-preview-head/**'
- - 'stl-preview-base/**'
+ branches:
+ - '**'
+ - '!integrated/**'
+ - '!stl-preview-head/**'
+ - '!stl-preview-base/**'
+ - '!generated'
+ - '!codegen/**'
+ - 'codegen/stl/**'
pull_request:
branches-ignore:
- 'stl-preview-head/**'
diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index f996350..a696b6a 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
{
- ".": "0.1.0-alpha.35"
+ ".": "0.1.0-alpha.36"
}
\ No newline at end of file
diff --git a/.stats.yml b/.stats.yml
index f98f9e8..18cf9ec 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
configured_endpoints: 20
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/brand-dev%2Fbrand.dev-584d3486a6c5bf7b68dcaacb0bde2ef5f648c158e5c5ebccc7a7684d95abc832.yml
-openapi_spec_hash: 29a53e1f96a2c5d9407f1a0938e301bf
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/brand-dev%2Fbrand.dev-67e4ffa39d74649a6ae6b21e9f86cffa83c8a02d640ca6b4d4a3e619b54fbd38.yml
+openapi_spec_hash: 762e7ea7ae23297cc6b01f600a485410
config_hash: 4cd3173ea1cce7183640aae49cfbb374
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b6492a8..28cdd99 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,23 @@
# Changelog
+## 0.1.0-alpha.36 (2026-03-18)
+
+Full Changelog: [v0.1.0-alpha.35...v0.1.0-alpha.36](https://github.com/brand-dot-dev/java-sdk/compare/v0.1.0-alpha.35...v0.1.0-alpha.36)
+
+### Features
+
+* **api:** api update ([2684d41](https://github.com/brand-dot-dev/java-sdk/commit/2684d410059e02e7dd09c288bd19e707a7221108))
+
+
+### Bug Fixes
+
+* **client:** incorrect `Retry-After` parsing ([95a5a2b](https://github.com/brand-dot-dev/java-sdk/commit/95a5a2b76191203166974e66729cd1142cf0fb4a))
+
+
+### Chores
+
+* **internal:** tweak CI branches ([7ab78d8](https://github.com/brand-dot-dev/java-sdk/commit/7ab78d8b427c70f67f20bd77b02e36d0ccf6d84b))
+
## 0.1.0-alpha.35 (2026-03-07)
Full Changelog: [v0.1.0-alpha.34...v0.1.0-alpha.35](https://github.com/brand-dot-dev/java-sdk/compare/v0.1.0-alpha.34...v0.1.0-alpha.35)
diff --git a/README.md b/README.md
index 8e37bc1..7d54127 100644
--- a/README.md
+++ b/README.md
@@ -2,8 +2,8 @@
-[](https://central.sonatype.com/artifact/com.branddev.api/brand-dev-java/0.1.0-alpha.35)
-[](https://javadoc.io/doc/com.branddev.api/brand-dev-java/0.1.0-alpha.35)
+[](https://central.sonatype.com/artifact/com.branddev.api/brand-dev-java/0.1.0-alpha.36)
+[](https://javadoc.io/doc/com.branddev.api/brand-dev-java/0.1.0-alpha.36)
@@ -22,7 +22,7 @@ Use the Brand Dev MCP Server to enable AI assistants to interact with this API,
-The REST API documentation can be found on [docs.brand.dev](https://docs.brand.dev/). Javadocs are available on [javadoc.io](https://javadoc.io/doc/com.branddev.api/brand-dev-java/0.1.0-alpha.35).
+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.36).
@@ -33,7 +33,7 @@ The REST API documentation can be found on [docs.brand.dev](https://docs.brand.d
### Gradle
```kotlin
-implementation("com.branddev.api:brand-dev-java:0.1.0-alpha.35")
+implementation("com.branddev.api:brand-dev-java:0.1.0-alpha.36")
```
### Maven
@@ -42,7 +42,7 @@ implementation("com.branddev.api:brand-dev-java:0.1.0-alpha.35")
com.branddev.api
brand-dev-java
- 0.1.0-alpha.35
+ 0.1.0-alpha.36
```
diff --git a/brand-dev-java-core/src/main/kotlin/com/branddev/api/core/http/RetryingHttpClient.kt b/brand-dev-java-core/src/main/kotlin/com/branddev/api/core/http/RetryingHttpClient.kt
index ce2a523..3054205 100644
--- a/brand-dev-java-core/src/main/kotlin/com/branddev/api/core/http/RetryingHttpClient.kt
+++ b/brand-dev-java-core/src/main/kotlin/com/branddev/api/core/http/RetryingHttpClient.kt
@@ -201,7 +201,7 @@ private constructor(
?: headers.values("Retry-After").getOrNull(0)?.let { retryAfter ->
retryAfter.toFloatOrNull()?.times(TimeUnit.SECONDS.toNanos(1))
?: try {
- ChronoUnit.MILLIS.between(
+ ChronoUnit.NANOS.between(
OffsetDateTime.now(clock),
OffsetDateTime.parse(
retryAfter,
diff --git a/brand-dev-java-core/src/main/kotlin/com/branddev/api/models/brand/BrandRetrieveByNameParams.kt b/brand-dev-java-core/src/main/kotlin/com/branddev/api/models/brand/BrandRetrieveByNameParams.kt
index 825b31d..c320160 100644
--- a/brand-dev-java-core/src/main/kotlin/com/branddev/api/models/brand/BrandRetrieveByNameParams.kt
+++ b/brand-dev-java-core/src/main/kotlin/com/branddev/api/models/brand/BrandRetrieveByNameParams.kt
@@ -21,6 +21,7 @@ import kotlin.jvm.optionals.getOrNull
class BrandRetrieveByNameParams
private constructor(
private val name: String,
+ private val countryGl: CountryGl?,
private val forceLanguage: ForceLanguage?,
private val maxSpeed: Boolean?,
private val timeoutMs: Long?,
@@ -34,6 +35,12 @@ private constructor(
*/
fun name(): String = name
+ /**
+ * Optional country code (GL parameter) to specify the country. This affects the geographic
+ * location used for search queries.
+ */
+ fun countryGl(): Optional = Optional.ofNullable(countryGl)
+
/** Optional parameter to force the language of the retrieved brand data. */
fun forceLanguage(): Optional = Optional.ofNullable(forceLanguage)
@@ -75,6 +82,7 @@ private constructor(
class Builder internal constructor() {
private var name: String? = null
+ private var countryGl: CountryGl? = null
private var forceLanguage: ForceLanguage? = null
private var maxSpeed: Boolean? = null
private var timeoutMs: Long? = null
@@ -84,6 +92,7 @@ private constructor(
@JvmSynthetic
internal fun from(brandRetrieveByNameParams: BrandRetrieveByNameParams) = apply {
name = brandRetrieveByNameParams.name
+ countryGl = brandRetrieveByNameParams.countryGl
forceLanguage = brandRetrieveByNameParams.forceLanguage
maxSpeed = brandRetrieveByNameParams.maxSpeed
timeoutMs = brandRetrieveByNameParams.timeoutMs
@@ -97,6 +106,15 @@ private constructor(
*/
fun name(name: String) = apply { this.name = name }
+ /**
+ * Optional country code (GL parameter) to specify the country. This affects the geographic
+ * location used for search queries.
+ */
+ fun countryGl(countryGl: CountryGl?) = apply { this.countryGl = countryGl }
+
+ /** Alias for calling [Builder.countryGl] with `countryGl.orElse(null)`. */
+ fun countryGl(countryGl: Optional) = countryGl(countryGl.getOrNull())
+
/** Optional parameter to force the language of the retrieved brand data. */
fun forceLanguage(forceLanguage: ForceLanguage?) = apply {
this.forceLanguage = forceLanguage
@@ -253,6 +271,7 @@ private constructor(
fun build(): BrandRetrieveByNameParams =
BrandRetrieveByNameParams(
checkRequired("name", name),
+ countryGl,
forceLanguage,
maxSpeed,
timeoutMs,
@@ -267,6 +286,7 @@ private constructor(
QueryParams.builder()
.apply {
put("name", name)
+ countryGl?.let { put("country_gl", it.toString()) }
forceLanguage?.let { put("force_language", it.toString()) }
maxSpeed?.let { put("maxSpeed", it.toString()) }
timeoutMs?.let { put("timeoutMS", it.toString()) }
@@ -274,6 +294,1561 @@ private constructor(
}
.build()
+ /**
+ * Optional country code (GL parameter) to specify the country. This affects the geographic
+ * location used for search queries.
+ */
+ class CountryGl @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 AD = of("ad")
+
+ @JvmField val AE = of("ae")
+
+ @JvmField val AF = of("af")
+
+ @JvmField val AG = of("ag")
+
+ @JvmField val AI = of("ai")
+
+ @JvmField val AL = of("al")
+
+ @JvmField val AM = of("am")
+
+ @JvmField val AN = of("an")
+
+ @JvmField val AO = of("ao")
+
+ @JvmField val AQ = of("aq")
+
+ @JvmField val AR = of("ar")
+
+ @JvmField val AS = of("as")
+
+ @JvmField val AT = of("at")
+
+ @JvmField val AU = of("au")
+
+ @JvmField val AW = of("aw")
+
+ @JvmField val AZ = of("az")
+
+ @JvmField val BA = of("ba")
+
+ @JvmField val BB = of("bb")
+
+ @JvmField val BD = of("bd")
+
+ @JvmField val BE = of("be")
+
+ @JvmField val BF = of("bf")
+
+ @JvmField val BG = of("bg")
+
+ @JvmField val BH = of("bh")
+
+ @JvmField val BI = of("bi")
+
+ @JvmField val BJ = of("bj")
+
+ @JvmField val BM = of("bm")
+
+ @JvmField val BN = of("bn")
+
+ @JvmField val BO = of("bo")
+
+ @JvmField val BR = of("br")
+
+ @JvmField val BS = of("bs")
+
+ @JvmField val BT = of("bt")
+
+ @JvmField val BV = of("bv")
+
+ @JvmField val BW = of("bw")
+
+ @JvmField val BY = of("by")
+
+ @JvmField val BZ = of("bz")
+
+ @JvmField val CA = of("ca")
+
+ @JvmField val CC = of("cc")
+
+ @JvmField val CD = of("cd")
+
+ @JvmField val CF = of("cf")
+
+ @JvmField val CG = of("cg")
+
+ @JvmField val CH = of("ch")
+
+ @JvmField val CI = of("ci")
+
+ @JvmField val CK = of("ck")
+
+ @JvmField val CL = of("cl")
+
+ @JvmField val CM = of("cm")
+
+ @JvmField val CN = of("cn")
+
+ @JvmField val CO = of("co")
+
+ @JvmField val CR = of("cr")
+
+ @JvmField val CU = of("cu")
+
+ @JvmField val CV = of("cv")
+
+ @JvmField val CX = of("cx")
+
+ @JvmField val CY = of("cy")
+
+ @JvmField val CZ = of("cz")
+
+ @JvmField val DE = of("de")
+
+ @JvmField val DJ = of("dj")
+
+ @JvmField val DK = of("dk")
+
+ @JvmField val DM = of("dm")
+
+ @JvmField val DO = of("do")
+
+ @JvmField val DZ = of("dz")
+
+ @JvmField val EC = of("ec")
+
+ @JvmField val EE = of("ee")
+
+ @JvmField val EG = of("eg")
+
+ @JvmField val EH = of("eh")
+
+ @JvmField val ER = of("er")
+
+ @JvmField val ES = of("es")
+
+ @JvmField val ET = of("et")
+
+ @JvmField val FI = of("fi")
+
+ @JvmField val FJ = of("fj")
+
+ @JvmField val FK = of("fk")
+
+ @JvmField val FM = of("fm")
+
+ @JvmField val FO = of("fo")
+
+ @JvmField val FR = of("fr")
+
+ @JvmField val GA = of("ga")
+
+ @JvmField val GB = of("gb")
+
+ @JvmField val GD = of("gd")
+
+ @JvmField val GE = of("ge")
+
+ @JvmField val GF = of("gf")
+
+ @JvmField val GH = of("gh")
+
+ @JvmField val GI = of("gi")
+
+ @JvmField val GL = of("gl")
+
+ @JvmField val GM = of("gm")
+
+ @JvmField val GN = of("gn")
+
+ @JvmField val GP = of("gp")
+
+ @JvmField val GQ = of("gq")
+
+ @JvmField val GR = of("gr")
+
+ @JvmField val GS = of("gs")
+
+ @JvmField val GT = of("gt")
+
+ @JvmField val GU = of("gu")
+
+ @JvmField val GW = of("gw")
+
+ @JvmField val GY = of("gy")
+
+ @JvmField val HK = of("hk")
+
+ @JvmField val HM = of("hm")
+
+ @JvmField val HN = of("hn")
+
+ @JvmField val HR = of("hr")
+
+ @JvmField val HT = of("ht")
+
+ @JvmField val HU = of("hu")
+
+ @JvmField val ID = of("id")
+
+ @JvmField val IE = of("ie")
+
+ @JvmField val IL = of("il")
+
+ @JvmField val IN = of("in")
+
+ @JvmField val IO = of("io")
+
+ @JvmField val IQ = of("iq")
+
+ @JvmField val IR = of("ir")
+
+ @JvmField val IS = of("is")
+
+ @JvmField val IT = of("it")
+
+ @JvmField val JM = of("jm")
+
+ @JvmField val JO = of("jo")
+
+ @JvmField val JP = of("jp")
+
+ @JvmField val KE = of("ke")
+
+ @JvmField val KG = of("kg")
+
+ @JvmField val KH = of("kh")
+
+ @JvmField val KI = of("ki")
+
+ @JvmField val KM = of("km")
+
+ @JvmField val KN = of("kn")
+
+ @JvmField val KP = of("kp")
+
+ @JvmField val KR = of("kr")
+
+ @JvmField val KW = of("kw")
+
+ @JvmField val KY = of("ky")
+
+ @JvmField val KZ = of("kz")
+
+ @JvmField val LA = of("la")
+
+ @JvmField val LB = of("lb")
+
+ @JvmField val LC = of("lc")
+
+ @JvmField val LI = of("li")
+
+ @JvmField val LK = of("lk")
+
+ @JvmField val LR = of("lr")
+
+ @JvmField val LS = of("ls")
+
+ @JvmField val LT = of("lt")
+
+ @JvmField val LU = of("lu")
+
+ @JvmField val LV = of("lv")
+
+ @JvmField val LY = of("ly")
+
+ @JvmField val MA = of("ma")
+
+ @JvmField val MC = of("mc")
+
+ @JvmField val MD = of("md")
+
+ @JvmField val MG = of("mg")
+
+ @JvmField val MH = of("mh")
+
+ @JvmField val MK = of("mk")
+
+ @JvmField val ML = of("ml")
+
+ @JvmField val MM = of("mm")
+
+ @JvmField val MN = of("mn")
+
+ @JvmField val MO = of("mo")
+
+ @JvmField val MP = of("mp")
+
+ @JvmField val MQ = of("mq")
+
+ @JvmField val MR = of("mr")
+
+ @JvmField val MS = of("ms")
+
+ @JvmField val MT = of("mt")
+
+ @JvmField val MU = of("mu")
+
+ @JvmField val MV = of("mv")
+
+ @JvmField val MW = of("mw")
+
+ @JvmField val MX = of("mx")
+
+ @JvmField val MY = of("my")
+
+ @JvmField val MZ = of("mz")
+
+ @JvmField val NA = of("na")
+
+ @JvmField val NC = of("nc")
+
+ @JvmField val NE = of("ne")
+
+ @JvmField val NF = of("nf")
+
+ @JvmField val NG = of("ng")
+
+ @JvmField val NI = of("ni")
+
+ @JvmField val NL = of("nl")
+
+ @JvmField val NO = of("no")
+
+ @JvmField val NP = of("np")
+
+ @JvmField val NR = of("nr")
+
+ @JvmField val NU = of("nu")
+
+ @JvmField val NZ = of("nz")
+
+ @JvmField val OM = of("om")
+
+ @JvmField val PA = of("pa")
+
+ @JvmField val PE = of("pe")
+
+ @JvmField val PF = of("pf")
+
+ @JvmField val PG = of("pg")
+
+ @JvmField val PH = of("ph")
+
+ @JvmField val PK = of("pk")
+
+ @JvmField val PL = of("pl")
+
+ @JvmField val PM = of("pm")
+
+ @JvmField val PN = of("pn")
+
+ @JvmField val PR = of("pr")
+
+ @JvmField val PS = of("ps")
+
+ @JvmField val PT = of("pt")
+
+ @JvmField val PW = of("pw")
+
+ @JvmField val PY = of("py")
+
+ @JvmField val QA = of("qa")
+
+ @JvmField val RE = of("re")
+
+ @JvmField val RO = of("ro")
+
+ @JvmField val RS = of("rs")
+
+ @JvmField val RU = of("ru")
+
+ @JvmField val RW = of("rw")
+
+ @JvmField val SA = of("sa")
+
+ @JvmField val SB = of("sb")
+
+ @JvmField val SC = of("sc")
+
+ @JvmField val SD = of("sd")
+
+ @JvmField val SE = of("se")
+
+ @JvmField val SG = of("sg")
+
+ @JvmField val SH = of("sh")
+
+ @JvmField val SI = of("si")
+
+ @JvmField val SJ = of("sj")
+
+ @JvmField val SK = of("sk")
+
+ @JvmField val SL = of("sl")
+
+ @JvmField val SM = of("sm")
+
+ @JvmField val SN = of("sn")
+
+ @JvmField val SO = of("so")
+
+ @JvmField val SR = of("sr")
+
+ @JvmField val ST = of("st")
+
+ @JvmField val SV = of("sv")
+
+ @JvmField val SY = of("sy")
+
+ @JvmField val SZ = of("sz")
+
+ @JvmField val TC = of("tc")
+
+ @JvmField val TD = of("td")
+
+ @JvmField val TF = of("tf")
+
+ @JvmField val TG = of("tg")
+
+ @JvmField val TH = of("th")
+
+ @JvmField val TJ = of("tj")
+
+ @JvmField val TK = of("tk")
+
+ @JvmField val TL = of("tl")
+
+ @JvmField val TM = of("tm")
+
+ @JvmField val TN = of("tn")
+
+ @JvmField val TO = of("to")
+
+ @JvmField val TR = of("tr")
+
+ @JvmField val TT = of("tt")
+
+ @JvmField val TV = of("tv")
+
+ @JvmField val TW = of("tw")
+
+ @JvmField val TZ = of("tz")
+
+ @JvmField val UA = of("ua")
+
+ @JvmField val UG = of("ug")
+
+ @JvmField val UM = of("um")
+
+ @JvmField val US = of("us")
+
+ @JvmField val UY = of("uy")
+
+ @JvmField val UZ = of("uz")
+
+ @JvmField val VA = of("va")
+
+ @JvmField val VC = of("vc")
+
+ @JvmField val VE = of("ve")
+
+ @JvmField val VG = of("vg")
+
+ @JvmField val VI = of("vi")
+
+ @JvmField val VN = of("vn")
+
+ @JvmField val VU = of("vu")
+
+ @JvmField val WF = of("wf")
+
+ @JvmField val WS = of("ws")
+
+ @JvmField val YE = of("ye")
+
+ @JvmField val YT = of("yt")
+
+ @JvmField val ZA = of("za")
+
+ @JvmField val ZM = of("zm")
+
+ @JvmField val ZW = of("zw")
+
+ @JvmStatic fun of(value: String) = CountryGl(JsonField.of(value))
+ }
+
+ /** An enum containing [CountryGl]'s known values. */
+ enum class Known {
+ AD,
+ AE,
+ AF,
+ AG,
+ AI,
+ AL,
+ AM,
+ AN,
+ AO,
+ AQ,
+ AR,
+ AS,
+ AT,
+ AU,
+ AW,
+ AZ,
+ BA,
+ BB,
+ BD,
+ BE,
+ BF,
+ BG,
+ BH,
+ BI,
+ BJ,
+ BM,
+ BN,
+ BO,
+ BR,
+ BS,
+ BT,
+ BV,
+ BW,
+ BY,
+ BZ,
+ CA,
+ CC,
+ CD,
+ CF,
+ CG,
+ CH,
+ CI,
+ CK,
+ CL,
+ CM,
+ CN,
+ CO,
+ CR,
+ CU,
+ CV,
+ CX,
+ CY,
+ CZ,
+ DE,
+ DJ,
+ DK,
+ DM,
+ DO,
+ DZ,
+ EC,
+ EE,
+ EG,
+ EH,
+ ER,
+ ES,
+ ET,
+ FI,
+ FJ,
+ FK,
+ FM,
+ FO,
+ FR,
+ GA,
+ GB,
+ GD,
+ GE,
+ GF,
+ GH,
+ GI,
+ GL,
+ GM,
+ GN,
+ GP,
+ GQ,
+ GR,
+ GS,
+ GT,
+ GU,
+ GW,
+ GY,
+ HK,
+ HM,
+ HN,
+ HR,
+ HT,
+ HU,
+ ID,
+ IE,
+ IL,
+ IN,
+ IO,
+ IQ,
+ IR,
+ IS,
+ IT,
+ JM,
+ JO,
+ JP,
+ KE,
+ KG,
+ KH,
+ KI,
+ KM,
+ KN,
+ KP,
+ KR,
+ KW,
+ KY,
+ KZ,
+ LA,
+ LB,
+ LC,
+ LI,
+ LK,
+ LR,
+ LS,
+ LT,
+ LU,
+ LV,
+ LY,
+ MA,
+ MC,
+ MD,
+ MG,
+ MH,
+ MK,
+ ML,
+ MM,
+ MN,
+ MO,
+ MP,
+ MQ,
+ MR,
+ MS,
+ MT,
+ MU,
+ MV,
+ MW,
+ MX,
+ MY,
+ MZ,
+ NA,
+ NC,
+ NE,
+ NF,
+ NG,
+ NI,
+ NL,
+ NO,
+ NP,
+ NR,
+ NU,
+ NZ,
+ OM,
+ PA,
+ PE,
+ PF,
+ PG,
+ PH,
+ PK,
+ PL,
+ PM,
+ PN,
+ PR,
+ PS,
+ PT,
+ PW,
+ PY,
+ QA,
+ RE,
+ RO,
+ RS,
+ RU,
+ RW,
+ SA,
+ SB,
+ SC,
+ SD,
+ SE,
+ SG,
+ SH,
+ SI,
+ SJ,
+ SK,
+ SL,
+ SM,
+ SN,
+ SO,
+ SR,
+ ST,
+ SV,
+ SY,
+ SZ,
+ TC,
+ TD,
+ TF,
+ TG,
+ TH,
+ TJ,
+ TK,
+ TL,
+ TM,
+ TN,
+ TO,
+ TR,
+ TT,
+ TV,
+ TW,
+ TZ,
+ UA,
+ UG,
+ UM,
+ US,
+ UY,
+ UZ,
+ VA,
+ VC,
+ VE,
+ VG,
+ VI,
+ VN,
+ VU,
+ WF,
+ WS,
+ YE,
+ YT,
+ ZA,
+ ZM,
+ ZW,
+ }
+
+ /**
+ * An enum containing [CountryGl]'s known values, as well as an [_UNKNOWN] member.
+ *
+ * An instance of [CountryGl] 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 {
+ AD,
+ AE,
+ AF,
+ AG,
+ AI,
+ AL,
+ AM,
+ AN,
+ AO,
+ AQ,
+ AR,
+ AS,
+ AT,
+ AU,
+ AW,
+ AZ,
+ BA,
+ BB,
+ BD,
+ BE,
+ BF,
+ BG,
+ BH,
+ BI,
+ BJ,
+ BM,
+ BN,
+ BO,
+ BR,
+ BS,
+ BT,
+ BV,
+ BW,
+ BY,
+ BZ,
+ CA,
+ CC,
+ CD,
+ CF,
+ CG,
+ CH,
+ CI,
+ CK,
+ CL,
+ CM,
+ CN,
+ CO,
+ CR,
+ CU,
+ CV,
+ CX,
+ CY,
+ CZ,
+ DE,
+ DJ,
+ DK,
+ DM,
+ DO,
+ DZ,
+ EC,
+ EE,
+ EG,
+ EH,
+ ER,
+ ES,
+ ET,
+ FI,
+ FJ,
+ FK,
+ FM,
+ FO,
+ FR,
+ GA,
+ GB,
+ GD,
+ GE,
+ GF,
+ GH,
+ GI,
+ GL,
+ GM,
+ GN,
+ GP,
+ GQ,
+ GR,
+ GS,
+ GT,
+ GU,
+ GW,
+ GY,
+ HK,
+ HM,
+ HN,
+ HR,
+ HT,
+ HU,
+ ID,
+ IE,
+ IL,
+ IN,
+ IO,
+ IQ,
+ IR,
+ IS,
+ IT,
+ JM,
+ JO,
+ JP,
+ KE,
+ KG,
+ KH,
+ KI,
+ KM,
+ KN,
+ KP,
+ KR,
+ KW,
+ KY,
+ KZ,
+ LA,
+ LB,
+ LC,
+ LI,
+ LK,
+ LR,
+ LS,
+ LT,
+ LU,
+ LV,
+ LY,
+ MA,
+ MC,
+ MD,
+ MG,
+ MH,
+ MK,
+ ML,
+ MM,
+ MN,
+ MO,
+ MP,
+ MQ,
+ MR,
+ MS,
+ MT,
+ MU,
+ MV,
+ MW,
+ MX,
+ MY,
+ MZ,
+ NA,
+ NC,
+ NE,
+ NF,
+ NG,
+ NI,
+ NL,
+ NO,
+ NP,
+ NR,
+ NU,
+ NZ,
+ OM,
+ PA,
+ PE,
+ PF,
+ PG,
+ PH,
+ PK,
+ PL,
+ PM,
+ PN,
+ PR,
+ PS,
+ PT,
+ PW,
+ PY,
+ QA,
+ RE,
+ RO,
+ RS,
+ RU,
+ RW,
+ SA,
+ SB,
+ SC,
+ SD,
+ SE,
+ SG,
+ SH,
+ SI,
+ SJ,
+ SK,
+ SL,
+ SM,
+ SN,
+ SO,
+ SR,
+ ST,
+ SV,
+ SY,
+ SZ,
+ TC,
+ TD,
+ TF,
+ TG,
+ TH,
+ TJ,
+ TK,
+ TL,
+ TM,
+ TN,
+ TO,
+ TR,
+ TT,
+ TV,
+ TW,
+ TZ,
+ UA,
+ UG,
+ UM,
+ US,
+ UY,
+ UZ,
+ VA,
+ VC,
+ VE,
+ VG,
+ VI,
+ VN,
+ VU,
+ WF,
+ WS,
+ YE,
+ YT,
+ ZA,
+ ZM,
+ ZW,
+ /**
+ * An enum member indicating that [CountryGl] 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) {
+ AD -> Value.AD
+ AE -> Value.AE
+ AF -> Value.AF
+ AG -> Value.AG
+ AI -> Value.AI
+ AL -> Value.AL
+ AM -> Value.AM
+ AN -> Value.AN
+ AO -> Value.AO
+ AQ -> Value.AQ
+ AR -> Value.AR
+ AS -> Value.AS
+ AT -> Value.AT
+ AU -> Value.AU
+ AW -> Value.AW
+ AZ -> Value.AZ
+ BA -> Value.BA
+ BB -> Value.BB
+ BD -> Value.BD
+ BE -> Value.BE
+ BF -> Value.BF
+ BG -> Value.BG
+ BH -> Value.BH
+ BI -> Value.BI
+ BJ -> Value.BJ
+ BM -> Value.BM
+ BN -> Value.BN
+ BO -> Value.BO
+ BR -> Value.BR
+ BS -> Value.BS
+ BT -> Value.BT
+ BV -> Value.BV
+ BW -> Value.BW
+ BY -> Value.BY
+ BZ -> Value.BZ
+ CA -> Value.CA
+ CC -> Value.CC
+ CD -> Value.CD
+ CF -> Value.CF
+ CG -> Value.CG
+ CH -> Value.CH
+ CI -> Value.CI
+ CK -> Value.CK
+ CL -> Value.CL
+ CM -> Value.CM
+ CN -> Value.CN
+ CO -> Value.CO
+ CR -> Value.CR
+ CU -> Value.CU
+ CV -> Value.CV
+ CX -> Value.CX
+ CY -> Value.CY
+ CZ -> Value.CZ
+ DE -> Value.DE
+ DJ -> Value.DJ
+ DK -> Value.DK
+ DM -> Value.DM
+ DO -> Value.DO
+ DZ -> Value.DZ
+ EC -> Value.EC
+ EE -> Value.EE
+ EG -> Value.EG
+ EH -> Value.EH
+ ER -> Value.ER
+ ES -> Value.ES
+ ET -> Value.ET
+ FI -> Value.FI
+ FJ -> Value.FJ
+ FK -> Value.FK
+ FM -> Value.FM
+ FO -> Value.FO
+ FR -> Value.FR
+ GA -> Value.GA
+ GB -> Value.GB
+ GD -> Value.GD
+ GE -> Value.GE
+ GF -> Value.GF
+ GH -> Value.GH
+ GI -> Value.GI
+ GL -> Value.GL
+ GM -> Value.GM
+ GN -> Value.GN
+ GP -> Value.GP
+ GQ -> Value.GQ
+ GR -> Value.GR
+ GS -> Value.GS
+ GT -> Value.GT
+ GU -> Value.GU
+ GW -> Value.GW
+ GY -> Value.GY
+ HK -> Value.HK
+ HM -> Value.HM
+ HN -> Value.HN
+ HR -> Value.HR
+ HT -> Value.HT
+ HU -> Value.HU
+ ID -> Value.ID
+ IE -> Value.IE
+ IL -> Value.IL
+ IN -> Value.IN
+ IO -> Value.IO
+ IQ -> Value.IQ
+ IR -> Value.IR
+ IS -> Value.IS
+ IT -> Value.IT
+ JM -> Value.JM
+ JO -> Value.JO
+ JP -> Value.JP
+ KE -> Value.KE
+ KG -> Value.KG
+ KH -> Value.KH
+ KI -> Value.KI
+ KM -> Value.KM
+ KN -> Value.KN
+ KP -> Value.KP
+ KR -> Value.KR
+ KW -> Value.KW
+ KY -> Value.KY
+ KZ -> Value.KZ
+ LA -> Value.LA
+ LB -> Value.LB
+ LC -> Value.LC
+ LI -> Value.LI
+ LK -> Value.LK
+ LR -> Value.LR
+ LS -> Value.LS
+ LT -> Value.LT
+ LU -> Value.LU
+ LV -> Value.LV
+ LY -> Value.LY
+ MA -> Value.MA
+ MC -> Value.MC
+ MD -> Value.MD
+ MG -> Value.MG
+ MH -> Value.MH
+ MK -> Value.MK
+ ML -> Value.ML
+ MM -> Value.MM
+ MN -> Value.MN
+ MO -> Value.MO
+ MP -> Value.MP
+ MQ -> Value.MQ
+ MR -> Value.MR
+ MS -> Value.MS
+ MT -> Value.MT
+ MU -> Value.MU
+ MV -> Value.MV
+ MW -> Value.MW
+ MX -> Value.MX
+ MY -> Value.MY
+ MZ -> Value.MZ
+ NA -> Value.NA
+ NC -> Value.NC
+ NE -> Value.NE
+ NF -> Value.NF
+ NG -> Value.NG
+ NI -> Value.NI
+ NL -> Value.NL
+ NO -> Value.NO
+ NP -> Value.NP
+ NR -> Value.NR
+ NU -> Value.NU
+ NZ -> Value.NZ
+ OM -> Value.OM
+ PA -> Value.PA
+ PE -> Value.PE
+ PF -> Value.PF
+ PG -> Value.PG
+ PH -> Value.PH
+ PK -> Value.PK
+ PL -> Value.PL
+ PM -> Value.PM
+ PN -> Value.PN
+ PR -> Value.PR
+ PS -> Value.PS
+ PT -> Value.PT
+ PW -> Value.PW
+ PY -> Value.PY
+ QA -> Value.QA
+ RE -> Value.RE
+ RO -> Value.RO
+ RS -> Value.RS
+ RU -> Value.RU
+ RW -> Value.RW
+ SA -> Value.SA
+ SB -> Value.SB
+ SC -> Value.SC
+ SD -> Value.SD
+ SE -> Value.SE
+ SG -> Value.SG
+ SH -> Value.SH
+ SI -> Value.SI
+ SJ -> Value.SJ
+ SK -> Value.SK
+ SL -> Value.SL
+ SM -> Value.SM
+ SN -> Value.SN
+ SO -> Value.SO
+ SR -> Value.SR
+ ST -> Value.ST
+ SV -> Value.SV
+ SY -> Value.SY
+ SZ -> Value.SZ
+ TC -> Value.TC
+ TD -> Value.TD
+ TF -> Value.TF
+ TG -> Value.TG
+ TH -> Value.TH
+ TJ -> Value.TJ
+ TK -> Value.TK
+ TL -> Value.TL
+ TM -> Value.TM
+ TN -> Value.TN
+ TO -> Value.TO
+ TR -> Value.TR
+ TT -> Value.TT
+ TV -> Value.TV
+ TW -> Value.TW
+ TZ -> Value.TZ
+ UA -> Value.UA
+ UG -> Value.UG
+ UM -> Value.UM
+ US -> Value.US
+ UY -> Value.UY
+ UZ -> Value.UZ
+ VA -> Value.VA
+ VC -> Value.VC
+ VE -> Value.VE
+ VG -> Value.VG
+ VI -> Value.VI
+ VN -> Value.VN
+ VU -> Value.VU
+ WF -> Value.WF
+ WS -> Value.WS
+ YE -> Value.YE
+ YT -> Value.YT
+ ZA -> Value.ZA
+ ZM -> Value.ZM
+ ZW -> Value.ZW
+ 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) {
+ AD -> Known.AD
+ AE -> Known.AE
+ AF -> Known.AF
+ AG -> Known.AG
+ AI -> Known.AI
+ AL -> Known.AL
+ AM -> Known.AM
+ AN -> Known.AN
+ AO -> Known.AO
+ AQ -> Known.AQ
+ AR -> Known.AR
+ AS -> Known.AS
+ AT -> Known.AT
+ AU -> Known.AU
+ AW -> Known.AW
+ AZ -> Known.AZ
+ BA -> Known.BA
+ BB -> Known.BB
+ BD -> Known.BD
+ BE -> Known.BE
+ BF -> Known.BF
+ BG -> Known.BG
+ BH -> Known.BH
+ BI -> Known.BI
+ BJ -> Known.BJ
+ BM -> Known.BM
+ BN -> Known.BN
+ BO -> Known.BO
+ BR -> Known.BR
+ BS -> Known.BS
+ BT -> Known.BT
+ BV -> Known.BV
+ BW -> Known.BW
+ BY -> Known.BY
+ BZ -> Known.BZ
+ CA -> Known.CA
+ CC -> Known.CC
+ CD -> Known.CD
+ CF -> Known.CF
+ CG -> Known.CG
+ CH -> Known.CH
+ CI -> Known.CI
+ CK -> Known.CK
+ CL -> Known.CL
+ CM -> Known.CM
+ CN -> Known.CN
+ CO -> Known.CO
+ CR -> Known.CR
+ CU -> Known.CU
+ CV -> Known.CV
+ CX -> Known.CX
+ CY -> Known.CY
+ CZ -> Known.CZ
+ DE -> Known.DE
+ DJ -> Known.DJ
+ DK -> Known.DK
+ DM -> Known.DM
+ DO -> Known.DO
+ DZ -> Known.DZ
+ EC -> Known.EC
+ EE -> Known.EE
+ EG -> Known.EG
+ EH -> Known.EH
+ ER -> Known.ER
+ ES -> Known.ES
+ ET -> Known.ET
+ FI -> Known.FI
+ FJ -> Known.FJ
+ FK -> Known.FK
+ FM -> Known.FM
+ FO -> Known.FO
+ FR -> Known.FR
+ GA -> Known.GA
+ GB -> Known.GB
+ GD -> Known.GD
+ GE -> Known.GE
+ GF -> Known.GF
+ GH -> Known.GH
+ GI -> Known.GI
+ GL -> Known.GL
+ GM -> Known.GM
+ GN -> Known.GN
+ GP -> Known.GP
+ GQ -> Known.GQ
+ GR -> Known.GR
+ GS -> Known.GS
+ GT -> Known.GT
+ GU -> Known.GU
+ GW -> Known.GW
+ GY -> Known.GY
+ HK -> Known.HK
+ HM -> Known.HM
+ HN -> Known.HN
+ HR -> Known.HR
+ HT -> Known.HT
+ HU -> Known.HU
+ ID -> Known.ID
+ IE -> Known.IE
+ IL -> Known.IL
+ IN -> Known.IN
+ IO -> Known.IO
+ IQ -> Known.IQ
+ IR -> Known.IR
+ IS -> Known.IS
+ IT -> Known.IT
+ JM -> Known.JM
+ JO -> Known.JO
+ JP -> Known.JP
+ KE -> Known.KE
+ KG -> Known.KG
+ KH -> Known.KH
+ KI -> Known.KI
+ KM -> Known.KM
+ KN -> Known.KN
+ KP -> Known.KP
+ KR -> Known.KR
+ KW -> Known.KW
+ KY -> Known.KY
+ KZ -> Known.KZ
+ LA -> Known.LA
+ LB -> Known.LB
+ LC -> Known.LC
+ LI -> Known.LI
+ LK -> Known.LK
+ LR -> Known.LR
+ LS -> Known.LS
+ LT -> Known.LT
+ LU -> Known.LU
+ LV -> Known.LV
+ LY -> Known.LY
+ MA -> Known.MA
+ MC -> Known.MC
+ MD -> Known.MD
+ MG -> Known.MG
+ MH -> Known.MH
+ MK -> Known.MK
+ ML -> Known.ML
+ MM -> Known.MM
+ MN -> Known.MN
+ MO -> Known.MO
+ MP -> Known.MP
+ MQ -> Known.MQ
+ MR -> Known.MR
+ MS -> Known.MS
+ MT -> Known.MT
+ MU -> Known.MU
+ MV -> Known.MV
+ MW -> Known.MW
+ MX -> Known.MX
+ MY -> Known.MY
+ MZ -> Known.MZ
+ NA -> Known.NA
+ NC -> Known.NC
+ NE -> Known.NE
+ NF -> Known.NF
+ NG -> Known.NG
+ NI -> Known.NI
+ NL -> Known.NL
+ NO -> Known.NO
+ NP -> Known.NP
+ NR -> Known.NR
+ NU -> Known.NU
+ NZ -> Known.NZ
+ OM -> Known.OM
+ PA -> Known.PA
+ PE -> Known.PE
+ PF -> Known.PF
+ PG -> Known.PG
+ PH -> Known.PH
+ PK -> Known.PK
+ PL -> Known.PL
+ PM -> Known.PM
+ PN -> Known.PN
+ PR -> Known.PR
+ PS -> Known.PS
+ PT -> Known.PT
+ PW -> Known.PW
+ PY -> Known.PY
+ QA -> Known.QA
+ RE -> Known.RE
+ RO -> Known.RO
+ RS -> Known.RS
+ RU -> Known.RU
+ RW -> Known.RW
+ SA -> Known.SA
+ SB -> Known.SB
+ SC -> Known.SC
+ SD -> Known.SD
+ SE -> Known.SE
+ SG -> Known.SG
+ SH -> Known.SH
+ SI -> Known.SI
+ SJ -> Known.SJ
+ SK -> Known.SK
+ SL -> Known.SL
+ SM -> Known.SM
+ SN -> Known.SN
+ SO -> Known.SO
+ SR -> Known.SR
+ ST -> Known.ST
+ SV -> Known.SV
+ SY -> Known.SY
+ SZ -> Known.SZ
+ TC -> Known.TC
+ TD -> Known.TD
+ TF -> Known.TF
+ TG -> Known.TG
+ TH -> Known.TH
+ TJ -> Known.TJ
+ TK -> Known.TK
+ TL -> Known.TL
+ TM -> Known.TM
+ TN -> Known.TN
+ TO -> Known.TO
+ TR -> Known.TR
+ TT -> Known.TT
+ TV -> Known.TV
+ TW -> Known.TW
+ TZ -> Known.TZ
+ UA -> Known.UA
+ UG -> Known.UG
+ UM -> Known.UM
+ US -> Known.US
+ UY -> Known.UY
+ UZ -> Known.UZ
+ VA -> Known.VA
+ VC -> Known.VC
+ VE -> Known.VE
+ VG -> Known.VG
+ VI -> Known.VI
+ VN -> Known.VN
+ VU -> Known.VU
+ WF -> Known.WF
+ WS -> Known.WS
+ YE -> Known.YE
+ YT -> Known.YT
+ ZA -> Known.ZA
+ ZM -> Known.ZM
+ ZW -> Known.ZW
+ else -> throw BrandDevInvalidDataException("Unknown CountryGl: $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(): CountryGl = 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 CountryGl && value == other.value
+ }
+
+ override fun hashCode() = value.hashCode()
+
+ override fun toString() = value.toString()
+ }
+
/** Optional parameter to force the language of the retrieved brand data. */
class ForceLanguage @JsonCreator private constructor(private val value: JsonField) :
Enum {
@@ -713,6 +2288,7 @@ private constructor(
return other is BrandRetrieveByNameParams &&
name == other.name &&
+ countryGl == other.countryGl &&
forceLanguage == other.forceLanguage &&
maxSpeed == other.maxSpeed &&
timeoutMs == other.timeoutMs &&
@@ -723,6 +2299,7 @@ private constructor(
override fun hashCode(): Int =
Objects.hash(
name,
+ countryGl,
forceLanguage,
maxSpeed,
timeoutMs,
@@ -731,5 +2308,5 @@ private constructor(
)
override fun toString() =
- "BrandRetrieveByNameParams{name=$name, forceLanguage=$forceLanguage, maxSpeed=$maxSpeed, timeoutMs=$timeoutMs, additionalHeaders=$additionalHeaders, additionalQueryParams=$additionalQueryParams}"
+ "BrandRetrieveByNameParams{name=$name, countryGl=$countryGl, forceLanguage=$forceLanguage, maxSpeed=$maxSpeed, timeoutMs=$timeoutMs, additionalHeaders=$additionalHeaders, additionalQueryParams=$additionalQueryParams}"
}
diff --git a/brand-dev-java-core/src/test/kotlin/com/branddev/api/core/http/RetryingHttpClientTest.kt b/brand-dev-java-core/src/test/kotlin/com/branddev/api/core/http/RetryingHttpClientTest.kt
index fabfd84..cf8eff9 100644
--- a/brand-dev-java-core/src/test/kotlin/com/branddev/api/core/http/RetryingHttpClientTest.kt
+++ b/brand-dev-java-core/src/test/kotlin/com/branddev/api/core/http/RetryingHttpClientTest.kt
@@ -20,7 +20,11 @@ import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo
import com.github.tomakehurst.wiremock.junit5.WireMockTest
import com.github.tomakehurst.wiremock.stubbing.Scenario
import java.io.InputStream
+import java.time.Clock
import java.time.Duration
+import java.time.OffsetDateTime
+import java.time.ZoneOffset
+import java.time.format.DateTimeFormatter
import java.util.concurrent.CompletableFuture
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.BeforeEach
@@ -36,6 +40,21 @@ internal class RetryingHttpClientTest {
private lateinit var baseUrl: String
private lateinit var httpClient: HttpClient
+ private class RecordingSleeper : Sleeper {
+ val durations = mutableListOf()
+
+ override fun sleep(duration: Duration) {
+ durations.add(duration)
+ }
+
+ override fun sleepAsync(duration: Duration): CompletableFuture {
+ durations.add(duration)
+ return CompletableFuture.completedFuture(null)
+ }
+
+ override fun close() {}
+ }
+
@BeforeEach
fun beforeEach(wmRuntimeInfo: WireMockRuntimeInfo) {
baseUrl = wmRuntimeInfo.httpBaseUrl
@@ -86,7 +105,8 @@ internal class RetryingHttpClientTest {
@ValueSource(booleans = [false, true])
fun execute(async: Boolean) {
stubFor(post(urlPathEqualTo("/something")).willReturn(ok()))
- val retryingClient = retryingHttpClientBuilder().build()
+ val sleeper = RecordingSleeper()
+ val retryingClient = retryingHttpClientBuilder(sleeper).build()
val response =
retryingClient.execute(
@@ -100,6 +120,7 @@ internal class RetryingHttpClientTest {
assertThat(response.statusCode()).isEqualTo(200)
verify(1, postRequestedFor(urlPathEqualTo("/something")))
+ assertThat(sleeper.durations).isEmpty()
assertNoResponseLeaks()
}
@@ -111,8 +132,12 @@ internal class RetryingHttpClientTest {
.withHeader("X-Some-Header", matching("stainless-java-retry-.+"))
.willReturn(ok())
)
+ val sleeper = RecordingSleeper()
val retryingClient =
- retryingHttpClientBuilder().maxRetries(2).idempotencyHeader("X-Some-Header").build()
+ retryingHttpClientBuilder(sleeper)
+ .maxRetries(2)
+ .idempotencyHeader("X-Some-Header")
+ .build()
val response =
retryingClient.execute(
@@ -126,20 +151,20 @@ internal class RetryingHttpClientTest {
assertThat(response.statusCode()).isEqualTo(200)
verify(1, postRequestedFor(urlPathEqualTo("/something")))
+ assertThat(sleeper.durations).isEmpty()
assertNoResponseLeaks()
}
@ParameterizedTest
@ValueSource(booleans = [false, true])
fun execute_withRetryAfterHeader(async: Boolean) {
+ val retryAfterDate = "Wed, 21 Oct 2015 07:28:00 GMT"
stubFor(
post(urlPathEqualTo("/something"))
// First we fail with a retry after header given as a date
.inScenario("foo")
.whenScenarioStateIs(Scenario.STARTED)
- .willReturn(
- serviceUnavailable().withHeader("Retry-After", "Wed, 21 Oct 2015 07:28:00 GMT")
- )
+ .willReturn(serviceUnavailable().withHeader("Retry-After", retryAfterDate))
.willSetStateTo("RETRY_AFTER_DATE")
)
stubFor(
@@ -158,7 +183,13 @@ internal class RetryingHttpClientTest {
.willReturn(ok())
.willSetStateTo("COMPLETED")
)
- val retryingClient = retryingHttpClientBuilder().maxRetries(2).build()
+ // Fix the clock to 5 seconds before the Retry-After date so the date-based backoff is
+ // deterministic.
+ val retryAfterDateTime =
+ OffsetDateTime.parse(retryAfterDate, DateTimeFormatter.RFC_1123_DATE_TIME)
+ val clock = Clock.fixed(retryAfterDateTime.minusSeconds(5).toInstant(), ZoneOffset.UTC)
+ val sleeper = RecordingSleeper()
+ val retryingClient = retryingHttpClientBuilder(sleeper, clock).maxRetries(2).build()
val response =
retryingClient.execute(
@@ -186,19 +217,20 @@ internal class RetryingHttpClientTest {
postRequestedFor(urlPathEqualTo("/something"))
.withHeader("x-stainless-retry-count", equalTo("2")),
)
+ assertThat(sleeper.durations)
+ .containsExactly(Duration.ofSeconds(5), Duration.ofMillis(1234))
assertNoResponseLeaks()
}
@ParameterizedTest
@ValueSource(booleans = [false, true])
fun execute_withOverwrittenRetryCountHeader(async: Boolean) {
+ val retryAfterDate = "Wed, 21 Oct 2015 07:28:00 GMT"
stubFor(
post(urlPathEqualTo("/something"))
.inScenario("foo") // first we fail with a retry after header given as a date
.whenScenarioStateIs(Scenario.STARTED)
- .willReturn(
- serviceUnavailable().withHeader("Retry-After", "Wed, 21 Oct 2015 07:28:00 GMT")
- )
+ .willReturn(serviceUnavailable().withHeader("Retry-After", retryAfterDate))
.willSetStateTo("RETRY_AFTER_DATE")
)
stubFor(
@@ -208,7 +240,11 @@ internal class RetryingHttpClientTest {
.willReturn(ok())
.willSetStateTo("COMPLETED")
)
- val retryingClient = retryingHttpClientBuilder().maxRetries(2).build()
+ val retryAfterDateTime =
+ OffsetDateTime.parse(retryAfterDate, DateTimeFormatter.RFC_1123_DATE_TIME)
+ val clock = Clock.fixed(retryAfterDateTime.minusSeconds(5).toInstant(), ZoneOffset.UTC)
+ val sleeper = RecordingSleeper()
+ val retryingClient = retryingHttpClientBuilder(sleeper, clock).maxRetries(2).build()
val response =
retryingClient.execute(
@@ -227,6 +263,7 @@ internal class RetryingHttpClientTest {
postRequestedFor(urlPathEqualTo("/something"))
.withHeader("x-stainless-retry-count", equalTo("42")),
)
+ assertThat(sleeper.durations).containsExactly(Duration.ofSeconds(5))
assertNoResponseLeaks()
}
@@ -247,7 +284,8 @@ internal class RetryingHttpClientTest {
.willReturn(ok())
.willSetStateTo("COMPLETED")
)
- val retryingClient = retryingHttpClientBuilder().maxRetries(1).build()
+ val sleeper = RecordingSleeper()
+ val retryingClient = retryingHttpClientBuilder(sleeper).maxRetries(1).build()
val response =
retryingClient.execute(
@@ -261,6 +299,7 @@ internal class RetryingHttpClientTest {
assertThat(response.statusCode()).isEqualTo(200)
verify(2, postRequestedFor(urlPathEqualTo("/something")))
+ assertThat(sleeper.durations).containsExactly(Duration.ofMillis(10))
assertNoResponseLeaks()
}
@@ -301,21 +340,12 @@ internal class RetryingHttpClientTest {
override fun close() = httpClient.close()
}
+ val sleeper = RecordingSleeper()
val retryingClient =
RetryingHttpClient.builder()
.httpClient(failingHttpClient)
.maxRetries(2)
- .sleeper(
- object : Sleeper {
-
- override fun sleep(duration: Duration) {}
-
- override fun sleepAsync(duration: Duration): CompletableFuture =
- CompletableFuture.completedFuture(null)
-
- override fun close() {}
- }
- )
+ .sleeper(sleeper)
.build()
val response =
@@ -339,25 +369,153 @@ internal class RetryingHttpClientTest {
postRequestedFor(urlPathEqualTo("/something"))
.withHeader("x-stainless-retry-count", equalTo("0")),
)
+ // Exponential backoff with jitter: 0.5s * jitter where jitter is in [0.75, 1.0].
+ assertThat(sleeper.durations).hasSize(1)
+ assertThat(sleeper.durations[0]).isBetween(Duration.ofMillis(375), Duration.ofMillis(500))
assertNoResponseLeaks()
}
- private fun retryingHttpClientBuilder() =
- RetryingHttpClient.builder()
- .httpClient(httpClient)
- // Use a no-op `Sleeper` to make the test fast.
- .sleeper(
- object : Sleeper {
+ @ParameterizedTest
+ @ValueSource(booleans = [false, true])
+ fun execute_withExponentialBackoff(async: Boolean) {
+ stubFor(post(urlPathEqualTo("/something")).willReturn(serviceUnavailable()))
+ val sleeper = RecordingSleeper()
+ val retryingClient = retryingHttpClientBuilder(sleeper).maxRetries(3).build()
+
+ val response =
+ retryingClient.execute(
+ HttpRequest.builder()
+ .method(HttpMethod.POST)
+ .baseUrl(baseUrl)
+ .addPathSegment("something")
+ .build(),
+ async,
+ )
- override fun sleep(duration: Duration) {}
+ // All retries exhausted; the last 503 response is returned.
+ assertThat(response.statusCode()).isEqualTo(503)
+ verify(4, postRequestedFor(urlPathEqualTo("/something")))
+ // Exponential backoff with jitter: backoff = min(0.5 * 2^(retries-1), 8) * jitter where
+ // jitter is in [0.75, 1.0].
+ assertThat(sleeper.durations).hasSize(3)
+ // retries=1: 0.5s * [0.75, 1.0]
+ assertThat(sleeper.durations[0]).isBetween(Duration.ofMillis(375), Duration.ofMillis(500))
+ // retries=2: 1.0s * [0.75, 1.0]
+ assertThat(sleeper.durations[1]).isBetween(Duration.ofMillis(750), Duration.ofMillis(1000))
+ // retries=3: 2.0s * [0.75, 1.0]
+ assertThat(sleeper.durations[2]).isBetween(Duration.ofMillis(1500), Duration.ofMillis(2000))
+ assertNoResponseLeaks()
+ }
- override fun sleepAsync(duration: Duration): CompletableFuture =
- CompletableFuture.completedFuture(null)
+ @ParameterizedTest
+ @ValueSource(booleans = [false, true])
+ fun execute_withExponentialBackoffCap(async: Boolean) {
+ stubFor(post(urlPathEqualTo("/something")).willReturn(serviceUnavailable()))
+ val sleeper = RecordingSleeper()
+ val retryingClient = retryingHttpClientBuilder(sleeper).maxRetries(6).build()
- override fun close() {}
- }
+ val response =
+ retryingClient.execute(
+ HttpRequest.builder()
+ .method(HttpMethod.POST)
+ .baseUrl(baseUrl)
+ .addPathSegment("something")
+ .build(),
+ async,
)
+ assertThat(response.statusCode()).isEqualTo(503)
+ verify(7, postRequestedFor(urlPathEqualTo("/something")))
+ assertThat(sleeper.durations).hasSize(6)
+ // retries=5: min(0.5 * 2^4, 8) = 8.0s * [0.75, 1.0]
+ assertThat(sleeper.durations[4]).isBetween(Duration.ofMillis(6000), Duration.ofMillis(8000))
+ // retries=6: min(0.5 * 2^5, 8) = min(16, 8) = 8.0s * [0.75, 1.0] (capped)
+ assertThat(sleeper.durations[5]).isBetween(Duration.ofMillis(6000), Duration.ofMillis(8000))
+ assertNoResponseLeaks()
+ }
+
+ @ParameterizedTest
+ @ValueSource(booleans = [false, true])
+ fun execute_withRetryAfterMsPriorityOverRetryAfter(async: Boolean) {
+ stubFor(
+ post(urlPathEqualTo("/something"))
+ .inScenario("foo")
+ .whenScenarioStateIs(Scenario.STARTED)
+ .willReturn(
+ serviceUnavailable()
+ .withHeader("Retry-After-Ms", "50")
+ .withHeader("Retry-After", "2")
+ )
+ .willSetStateTo("RETRY")
+ )
+ stubFor(
+ post(urlPathEqualTo("/something"))
+ .inScenario("foo")
+ .whenScenarioStateIs("RETRY")
+ .willReturn(ok())
+ .willSetStateTo("COMPLETED")
+ )
+ val sleeper = RecordingSleeper()
+ val retryingClient = retryingHttpClientBuilder(sleeper).maxRetries(1).build()
+
+ val response =
+ retryingClient.execute(
+ HttpRequest.builder()
+ .method(HttpMethod.POST)
+ .baseUrl(baseUrl)
+ .addPathSegment("something")
+ .build(),
+ async,
+ )
+
+ assertThat(response.statusCode()).isEqualTo(200)
+ // Retry-After-Ms (50ms) takes priority over Retry-After (2s).
+ assertThat(sleeper.durations).containsExactly(Duration.ofMillis(50))
+ assertNoResponseLeaks()
+ }
+
+ @ParameterizedTest
+ @ValueSource(booleans = [false, true])
+ fun execute_withRetryAfterUnparseable(async: Boolean) {
+ stubFor(
+ post(urlPathEqualTo("/something"))
+ .inScenario("foo")
+ .whenScenarioStateIs(Scenario.STARTED)
+ .willReturn(serviceUnavailable().withHeader("Retry-After", "not-a-date-or-number"))
+ .willSetStateTo("RETRY")
+ )
+ stubFor(
+ post(urlPathEqualTo("/something"))
+ .inScenario("foo")
+ .whenScenarioStateIs("RETRY")
+ .willReturn(ok())
+ .willSetStateTo("COMPLETED")
+ )
+ val sleeper = RecordingSleeper()
+ val retryingClient = retryingHttpClientBuilder(sleeper).maxRetries(1).build()
+
+ val response =
+ retryingClient.execute(
+ HttpRequest.builder()
+ .method(HttpMethod.POST)
+ .baseUrl(baseUrl)
+ .addPathSegment("something")
+ .build(),
+ async,
+ )
+
+ assertThat(response.statusCode()).isEqualTo(200)
+ // Unparseable Retry-After falls through to exponential backoff.
+ assertThat(sleeper.durations).hasSize(1)
+ assertThat(sleeper.durations[0]).isBetween(Duration.ofMillis(375), Duration.ofMillis(500))
+ assertNoResponseLeaks()
+ }
+
+ private fun retryingHttpClientBuilder(
+ sleeper: RecordingSleeper,
+ clock: Clock = Clock.systemUTC(),
+ ) = RetryingHttpClient.builder().httpClient(httpClient).sleeper(sleeper).clock(clock)
+
private fun HttpClient.execute(request: HttpRequest, async: Boolean): HttpResponse =
if (async) executeAsync(request).get() else execute(request)
diff --git a/brand-dev-java-core/src/test/kotlin/com/branddev/api/models/brand/BrandRetrieveByNameParamsTest.kt b/brand-dev-java-core/src/test/kotlin/com/branddev/api/models/brand/BrandRetrieveByNameParamsTest.kt
index bd3d663..9071f7c 100644
--- a/brand-dev-java-core/src/test/kotlin/com/branddev/api/models/brand/BrandRetrieveByNameParamsTest.kt
+++ b/brand-dev-java-core/src/test/kotlin/com/branddev/api/models/brand/BrandRetrieveByNameParamsTest.kt
@@ -12,6 +12,7 @@ internal class BrandRetrieveByNameParamsTest {
fun create() {
BrandRetrieveByNameParams.builder()
.name("xxx")
+ .countryGl(BrandRetrieveByNameParams.CountryGl.AD)
.forceLanguage(BrandRetrieveByNameParams.ForceLanguage.ALBANIAN)
.maxSpeed(true)
.timeoutMs(1000L)
@@ -23,6 +24,7 @@ internal class BrandRetrieveByNameParamsTest {
val params =
BrandRetrieveByNameParams.builder()
.name("xxx")
+ .countryGl(BrandRetrieveByNameParams.CountryGl.AD)
.forceLanguage(BrandRetrieveByNameParams.ForceLanguage.ALBANIAN)
.maxSpeed(true)
.timeoutMs(1000L)
@@ -34,6 +36,7 @@ internal class BrandRetrieveByNameParamsTest {
.isEqualTo(
QueryParams.builder()
.put("name", "xxx")
+ .put("country_gl", "ad")
.put("force_language", "albanian")
.put("maxSpeed", "true")
.put("timeoutMS", "1000")
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 92f995f..a270db9 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
@@ -260,6 +260,7 @@ internal class BrandServiceAsyncTest {
brandServiceAsync.retrieveByName(
BrandRetrieveByNameParams.builder()
.name("xxx")
+ .countryGl(BrandRetrieveByNameParams.CountryGl.AD)
.forceLanguage(BrandRetrieveByNameParams.ForceLanguage.ALBANIAN)
.maxSpeed(true)
.timeoutMs(1000L)
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 76ba299..b9d402d 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
@@ -248,6 +248,7 @@ internal class BrandServiceTest {
brandService.retrieveByName(
BrandRetrieveByNameParams.builder()
.name("xxx")
+ .countryGl(BrandRetrieveByNameParams.CountryGl.AD)
.forceLanguage(BrandRetrieveByNameParams.ForceLanguage.ALBANIAN)
.maxSpeed(true)
.timeoutMs(1000L)
diff --git a/build.gradle.kts b/build.gradle.kts
index f4b8e31..259a006 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -9,7 +9,7 @@ repositories {
allprojects {
group = "com.branddev.api"
- version = "0.1.0-alpha.35" // x-release-please-version
+ version = "0.1.0-alpha.36" // x-release-please-version
}
subprojects {