From d712abbfc3f4e8e54e8391ae34616bae45510b27 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 25 Nov 2025 16:50:33 +0000 Subject: [PATCH 01/25] feat(api): manual updates --- .github/workflows/publish-sonatype.yml | 41 ++++++++++++ .github/workflows/release-doctor.yml | 24 +++++++ .release-please-manifest.json | 3 + .stats.yml | 2 +- README.md | 14 +++- bin/check-release-environment | 33 +++++++++ build.gradle.kts | 2 +- .../src/main/kotlin/phoebe.publish.gradle.kts | 6 +- .../main/kotlin/com/phoebe/api/core/Check.kt | 2 +- release-please-config.json | 67 +++++++++++++++++++ 10 files changed, 187 insertions(+), 7 deletions(-) create mode 100644 .github/workflows/publish-sonatype.yml create mode 100644 .github/workflows/release-doctor.yml create mode 100644 .release-please-manifest.json create mode 100644 bin/check-release-environment create mode 100644 release-please-config.json diff --git a/.github/workflows/publish-sonatype.yml b/.github/workflows/publish-sonatype.yml new file mode 100644 index 0000000..48cce0b --- /dev/null +++ b/.github/workflows/publish-sonatype.yml @@ -0,0 +1,41 @@ +# This workflow is triggered when a GitHub release is created. +# It can also be run manually to re-publish to Sonatype in case it failed for some reason. +# You can run this workflow by navigating to https://www.github.com/phoebe-bird/phoebe-java/actions/workflows/publish-sonatype.yml +name: Publish Sonatype +on: + workflow_dispatch: + + release: + types: [published] + +jobs: + publish: + name: publish + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up Java + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: | + 8 + 21 + cache: gradle + + - name: Set up Gradle + uses: gradle/gradle-build-action@v2 + + - name: Publish to Sonatype + run: |- + export -- GPG_SIGNING_KEY_ID + printenv -- GPG_SIGNING_KEY | gpg --batch --passphrase-fd 3 --import 3<<< "$GPG_SIGNING_PASSWORD" + GPG_SIGNING_KEY_ID="$(gpg --with-colons --list-keys | awk -F : -- '/^pub:/ { getline; print "0x" substr($10, length($10) - 7) }')" + ./gradlew publish --no-configuration-cache + env: + SONATYPE_USERNAME: ${{ secrets.PHOEBE_SONATYPE_USERNAME || secrets.SONATYPE_USERNAME }} + SONATYPE_PASSWORD: ${{ secrets.PHOEBE_SONATYPE_PASSWORD || secrets.SONATYPE_PASSWORD }} + GPG_SIGNING_KEY: ${{ secrets.PHOEBE_SONATYPE_GPG_SIGNING_KEY || secrets.GPG_SIGNING_KEY }} + GPG_SIGNING_PASSWORD: ${{ secrets.PHOEBE_SONATYPE_GPG_SIGNING_PASSWORD || secrets.GPG_SIGNING_PASSWORD }} \ No newline at end of file diff --git a/.github/workflows/release-doctor.yml b/.github/workflows/release-doctor.yml new file mode 100644 index 0000000..e220472 --- /dev/null +++ b/.github/workflows/release-doctor.yml @@ -0,0 +1,24 @@ +name: Release Doctor +on: + pull_request: + branches: + - main + workflow_dispatch: + +jobs: + release_doctor: + name: release doctor + runs-on: ubuntu-latest + if: github.repository == 'phoebe-bird/phoebe-java' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || startsWith(github.head_ref, 'release-please') || github.head_ref == 'next') + + steps: + - uses: actions/checkout@v4 + + - name: Check release environment + run: | + bash ./bin/check-release-environment + env: + SONATYPE_USERNAME: ${{ secrets.PHOEBE_SONATYPE_USERNAME || secrets.SONATYPE_USERNAME }} + SONATYPE_PASSWORD: ${{ secrets.PHOEBE_SONATYPE_PASSWORD || secrets.SONATYPE_PASSWORD }} + GPG_SIGNING_KEY: ${{ secrets.PHOEBE_SONATYPE_GPG_SIGNING_KEY || secrets.GPG_SIGNING_KEY }} + GPG_SIGNING_PASSWORD: ${{ secrets.PHOEBE_SONATYPE_GPG_SIGNING_PASSWORD || secrets.GPG_SIGNING_PASSWORD }} diff --git a/.release-please-manifest.json b/.release-please-manifest.json new file mode 100644 index 0000000..c476280 --- /dev/null +++ b/.release-please-manifest.json @@ -0,0 +1,3 @@ +{ + ".": "0.0.1-alpha.0" +} \ No newline at end of file diff --git a/.stats.yml b/.stats.yml index 87b8e22..ca25ebc 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 25 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/personal-ijyk2f%2Fphoebe-3974e6147f98a3ce3fc4684fda2671e7a7dc7476ecdf42af7a5dc88725cc8f04.yml openapi_spec_hash: 9808c815a0b204fdee17a0abca108ab5 -config_hash: 90be8240e7a9b625b33329bb412c0bea +config_hash: 4f239d752d3041a8aa094ef5b66dbab6 diff --git a/README.md b/README.md index 9a45101..e694e59 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,26 @@ # Phoebe Java API Library + + [![Maven Central](https://img.shields.io/maven-central/v/com.phoebe.api/phoebe-java)](https://central.sonatype.com/artifact/com.phoebe.api/phoebe-java/0.0.1-alpha.0) [![javadoc](https://javadoc.io/badge2/com.phoebe.api/phoebe-java/0.0.1-alpha.0/javadoc.svg)](https://javadoc.io/doc/com.phoebe.api/phoebe-java/0.0.1-alpha.0) + + The Phoebe Java SDK provides convenient access to the [Phoebe REST API](https://science.ebird.org/en/use-ebird-data/download-ebird-data-products) from applications written in Java. It is generated with [Stainless](https://www.stainless.com/). + + The REST API documentation can be found on [science.ebird.org](https://science.ebird.org/en/use-ebird-data/download-ebird-data-products). Javadocs are available on [javadoc.io](https://javadoc.io/doc/com.phoebe.api/phoebe-java/0.0.1-alpha.0). + + ## Installation + + ### Gradle ```kotlin @@ -27,6 +37,8 @@ implementation("com.phoebe.api:phoebe-java:0.0.1-alpha.0") ``` + + ## Requirements This library requires Java 8 or later. @@ -583,4 +595,4 @@ This package generally follows [SemVer](https://semver.org/spec/v2.0.0.html) con We take backwards-compatibility seriously and work hard to ensure you can rely on a smooth upgrade experience. -We are keen for your feedback; please open an [issue](https://www.github.com/stainless-sdks/phoebe-java/issues) with questions, bugs, or suggestions. +We are keen for your feedback; please open an [issue](https://www.github.com/phoebe-bird/phoebe-java/issues) with questions, bugs, or suggestions. diff --git a/bin/check-release-environment b/bin/check-release-environment new file mode 100644 index 0000000..3a6a7b4 --- /dev/null +++ b/bin/check-release-environment @@ -0,0 +1,33 @@ +#!/usr/bin/env bash + +errors=() + +if [ -z "${SONATYPE_USERNAME}" ]; then + errors+=("The SONATYPE_USERNAME secret has not been set. Please set it in either this repository's secrets or your organization secrets") +fi + +if [ -z "${SONATYPE_PASSWORD}" ]; then + errors+=("The SONATYPE_PASSWORD secret has not been set. Please set it in either this repository's secrets or your organization secrets") +fi + +if [ -z "${GPG_SIGNING_KEY}" ]; then + errors+=("The GPG_SIGNING_KEY secret has not been set. Please set it in either this repository's secrets or your organization secrets") +fi + +if [ -z "${GPG_SIGNING_PASSWORD}" ]; then + errors+=("The GPG_SIGNING_PASSWORD secret has not been set. Please set it in either this repository's secrets or your organization secrets") +fi + +lenErrors=${#errors[@]} + +if [[ lenErrors -gt 0 ]]; then + echo -e "Found the following errors in the release environment:\n" + + for error in "${errors[@]}"; do + echo -e "- $error\n" + done + + exit 1 +fi + +echo "The environment is ready to push releases!" diff --git a/build.gradle.kts b/build.gradle.kts index 95d3c75..bccbd2e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -9,7 +9,7 @@ repositories { allprojects { group = "com.phoebe.api" - version = "0.0.1-alpha.0" + version = "0.0.1-alpha.0" // x-release-please-version } subprojects { diff --git a/buildSrc/src/main/kotlin/phoebe.publish.gradle.kts b/buildSrc/src/main/kotlin/phoebe.publish.gradle.kts index dd8ea61..3bf789a 100644 --- a/buildSrc/src/main/kotlin/phoebe.publish.gradle.kts +++ b/buildSrc/src/main/kotlin/phoebe.publish.gradle.kts @@ -27,9 +27,9 @@ configure { } scm { - connection.set("scm:git:git://github.com/stainless-sdks/phoebe-java.git") - developerConnection.set("scm:git:git://github.com/stainless-sdks/phoebe-java.git") - url.set("https://github.com/stainless-sdks/phoebe-java") + connection.set("scm:git:git://github.com/phoebe-bird/phoebe-java.git") + developerConnection.set("scm:git:git://github.com/phoebe-bird/phoebe-java.git") + url.set("https://github.com/phoebe-bird/phoebe-java") } versionMapping { diff --git a/phoebe-java-core/src/main/kotlin/com/phoebe/api/core/Check.kt b/phoebe-java-core/src/main/kotlin/com/phoebe/api/core/Check.kt index 295fcbc..e8c3e22 100644 --- a/phoebe-java-core/src/main/kotlin/com/phoebe/api/core/Check.kt +++ b/phoebe-java-core/src/main/kotlin/com/phoebe/api/core/Check.kt @@ -77,7 +77,7 @@ This can happen if you are either: Double-check that you are depending on compatible Jackson versions. -See https://www.github.com/stainless-sdks/phoebe-java#jackson for more information. +See https://www.github.com/phoebe-bird/phoebe-java#jackson for more information. """ .trimIndent() } diff --git a/release-please-config.json b/release-please-config.json new file mode 100644 index 0000000..8f98719 --- /dev/null +++ b/release-please-config.json @@ -0,0 +1,67 @@ +{ + "packages": { + ".": {} + }, + "$schema": "https://raw.githubusercontent.com/stainless-api/release-please/main/schemas/config.json", + "include-v-in-tag": true, + "include-component-in-tag": false, + "versioning": "prerelease", + "prerelease": true, + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": false, + "pull-request-header": "Automated Release PR", + "pull-request-title-pattern": "release: ${version}", + "changelog-sections": [ + { + "type": "feat", + "section": "Features" + }, + { + "type": "fix", + "section": "Bug Fixes" + }, + { + "type": "perf", + "section": "Performance Improvements" + }, + { + "type": "revert", + "section": "Reverts" + }, + { + "type": "chore", + "section": "Chores" + }, + { + "type": "docs", + "section": "Documentation" + }, + { + "type": "style", + "section": "Styles" + }, + { + "type": "refactor", + "section": "Refactors" + }, + { + "type": "test", + "section": "Tests", + "hidden": true + }, + { + "type": "build", + "section": "Build System" + }, + { + "type": "ci", + "section": "Continuous Integration", + "hidden": true + } + ], + "release-type": "simple", + "extra-files": [ + "README.md", + "build.gradle.kts" + ] +} \ No newline at end of file From 29fb6a253e46733a4c317823cfd1fa105d1dceeb Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 25 Nov 2025 16:51:22 +0000 Subject: [PATCH 02/25] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index ca25ebc..6913ec0 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 25 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/personal-ijyk2f%2Fphoebe-3974e6147f98a3ce3fc4684fda2671e7a7dc7476ecdf42af7a5dc88725cc8f04.yml openapi_spec_hash: 9808c815a0b204fdee17a0abca108ab5 -config_hash: 4f239d752d3041a8aa094ef5b66dbab6 +config_hash: 51d14778da545342df105240e103d7dd From 34e57eb9eb6d57c100803d3e4a9eae9bdc1dbe23 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 25 Nov 2025 16:55:28 +0000 Subject: [PATCH 03/25] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index 6913ec0..8c14db5 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 25 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/personal-ijyk2f%2Fphoebe-3974e6147f98a3ce3fc4684fda2671e7a7dc7476ecdf42af7a5dc88725cc8f04.yml openapi_spec_hash: 9808c815a0b204fdee17a0abca108ab5 -config_hash: 51d14778da545342df105240e103d7dd +config_hash: 17a571ff53308f9c391515734db807d2 From 5823abafce2a92fbb69a0f6c23db79a78b78f2a8 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 27 Nov 2025 06:13:10 +0000 Subject: [PATCH 04/25] fix(client): cancel okhttp call when future cancelled --- phoebe-java-client-okhttp/build.gradle.kts | 1 + .../phoebe/api/client/okhttp/OkHttpClient.kt | 34 ++++++++------ .../api/client/okhttp/OkHttpClientTest.kt | 44 +++++++++++++++++++ 3 files changed, 65 insertions(+), 14 deletions(-) create mode 100644 phoebe-java-client-okhttp/src/test/kotlin/com/phoebe/api/client/okhttp/OkHttpClientTest.kt diff --git a/phoebe-java-client-okhttp/build.gradle.kts b/phoebe-java-client-okhttp/build.gradle.kts index fc320fc..d69a5de 100644 --- a/phoebe-java-client-okhttp/build.gradle.kts +++ b/phoebe-java-client-okhttp/build.gradle.kts @@ -11,4 +11,5 @@ dependencies { testImplementation(kotlin("test")) testImplementation("org.assertj:assertj-core:3.25.3") + testImplementation("com.github.tomakehurst:wiremock-jre8:2.35.2") } diff --git a/phoebe-java-client-okhttp/src/main/kotlin/com/phoebe/api/client/okhttp/OkHttpClient.kt b/phoebe-java-client-okhttp/src/main/kotlin/com/phoebe/api/client/okhttp/OkHttpClient.kt index ad75691..32c9047 100644 --- a/phoebe-java-client-okhttp/src/main/kotlin/com/phoebe/api/client/okhttp/OkHttpClient.kt +++ b/phoebe-java-client-okhttp/src/main/kotlin/com/phoebe/api/client/okhttp/OkHttpClient.kt @@ -13,6 +13,7 @@ import java.io.IOException import java.io.InputStream import java.net.Proxy import java.time.Duration +import java.util.concurrent.CancellationException import java.util.concurrent.CompletableFuture import javax.net.ssl.HostnameVerifier import javax.net.ssl.SSLSocketFactory @@ -29,8 +30,8 @@ import okhttp3.Response import okhttp3.logging.HttpLoggingInterceptor import okio.BufferedSink -class OkHttpClient private constructor(private val okHttpClient: okhttp3.OkHttpClient) : - HttpClient { +class OkHttpClient +private constructor(@JvmSynthetic internal val okHttpClient: okhttp3.OkHttpClient) : HttpClient { override fun execute(request: HttpRequest, requestOptions: RequestOptions): HttpResponse { val call = newCall(request, requestOptions) @@ -50,20 +51,25 @@ class OkHttpClient private constructor(private val okHttpClient: okhttp3.OkHttpC ): CompletableFuture { val future = CompletableFuture() - request.body?.run { future.whenComplete { _, _ -> close() } } - - newCall(request, requestOptions) - .enqueue( - object : Callback { - override fun onResponse(call: Call, response: Response) { - future.complete(response.toResponse()) - } + val call = newCall(request, requestOptions) + call.enqueue( + object : Callback { + override fun onResponse(call: Call, response: Response) { + future.complete(response.toResponse()) + } - override fun onFailure(call: Call, e: IOException) { - future.completeExceptionally(PhoebeIoException("Request failed", e)) - } + override fun onFailure(call: Call, e: IOException) { + future.completeExceptionally(PhoebeIoException("Request failed", e)) } - ) + } + ) + + future.whenComplete { _, e -> + if (e is CancellationException) { + call.cancel() + } + request.body?.close() + } return future } diff --git a/phoebe-java-client-okhttp/src/test/kotlin/com/phoebe/api/client/okhttp/OkHttpClientTest.kt b/phoebe-java-client-okhttp/src/test/kotlin/com/phoebe/api/client/okhttp/OkHttpClientTest.kt new file mode 100644 index 0000000..5ba90f8 --- /dev/null +++ b/phoebe-java-client-okhttp/src/test/kotlin/com/phoebe/api/client/okhttp/OkHttpClientTest.kt @@ -0,0 +1,44 @@ +package com.phoebe.api.client.okhttp + +import com.github.tomakehurst.wiremock.client.WireMock.* +import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo +import com.github.tomakehurst.wiremock.junit5.WireMockTest +import com.phoebe.api.core.http.HttpMethod +import com.phoebe.api.core.http.HttpRequest +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.parallel.ResourceLock + +@WireMockTest +@ResourceLock("https://github.com/wiremock/wiremock/issues/169") +internal class OkHttpClientTest { + + private lateinit var baseUrl: String + private lateinit var httpClient: OkHttpClient + + @BeforeEach + fun beforeEach(wmRuntimeInfo: WireMockRuntimeInfo) { + baseUrl = wmRuntimeInfo.httpBaseUrl + httpClient = OkHttpClient.builder().build() + } + + @Test + fun executeAsync_whenFutureCancelled_cancelsUnderlyingCall() { + stubFor(post(urlPathEqualTo("/something")).willReturn(ok())) + val responseFuture = + httpClient.executeAsync( + HttpRequest.builder() + .method(HttpMethod.POST) + .baseUrl(baseUrl) + .addPathSegment("something") + .build() + ) + val call = httpClient.okHttpClient.dispatcher.runningCalls().single() + + responseFuture.cancel(false) + + // Should have cancelled the underlying call + assertThat(call.isCanceled()).isTrue() + } +} From fe8a02007be585c2c162442e968a4fe8909eb39e Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 3 Dec 2025 08:08:34 +0000 Subject: [PATCH 05/25] docs: remove `$` for better copy-pasteabality --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e694e59..8cffb72 100644 --- a/README.md +++ b/README.md @@ -232,13 +232,13 @@ The SDK uses the standard [OkHttp logging interceptor](https://github.com/square Enable logging by setting the `PHOEBE_LOG` environment variable to `info`: ```sh -$ export PHOEBE_LOG=info +export PHOEBE_LOG=info ``` Or to `debug` for more verbose logging: ```sh -$ export PHOEBE_LOG=debug +export PHOEBE_LOG=debug ``` ## ProGuard and R8 From a145b94467e13633c59889cccd0c26b0b3789a44 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 6 Jan 2026 07:24:55 +0000 Subject: [PATCH 06/25] feat(client): add `HttpRequest#url()` method --- LICENSE | 2 +- .../com/phoebe/api/core/http/HttpRequest.kt | 30 +++++ .../phoebe/api/core/http/HttpRequestTest.kt | 110 ++++++++++++++++++ 3 files changed, 141 insertions(+), 1 deletion(-) create mode 100644 phoebe-java-core/src/test/kotlin/com/phoebe/api/core/http/HttpRequestTest.kt diff --git a/LICENSE b/LICENSE index aa84a46..a69fada 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 Phoebe + Copyright 2026 Phoebe 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/phoebe-java-core/src/main/kotlin/com/phoebe/api/core/http/HttpRequest.kt b/phoebe-java-core/src/main/kotlin/com/phoebe/api/core/http/HttpRequest.kt index e398c05..6ddee7f 100644 --- a/phoebe-java-core/src/main/kotlin/com/phoebe/api/core/http/HttpRequest.kt +++ b/phoebe-java-core/src/main/kotlin/com/phoebe/api/core/http/HttpRequest.kt @@ -2,6 +2,7 @@ package com.phoebe.api.core.http import com.phoebe.api.core.checkRequired import com.phoebe.api.core.toImmutable +import java.net.URLEncoder class HttpRequest private constructor( @@ -13,6 +14,35 @@ private constructor( @get:JvmName("body") val body: HttpRequestBody?, ) { + fun url(): String = buildString { + append(baseUrl) + + pathSegments.forEach { segment -> + if (!endsWith("/")) { + append("/") + } + append(URLEncoder.encode(segment, "UTF-8")) + } + + if (queryParams.isEmpty()) { + return@buildString + } + + append("?") + var isFirst = true + queryParams.keys().forEach { key -> + queryParams.values(key).forEach { value -> + if (!isFirst) { + append("&") + } + append(URLEncoder.encode(key, "UTF-8")) + append("=") + append(URLEncoder.encode(value, "UTF-8")) + isFirst = false + } + } + } + fun toBuilder(): Builder = Builder().from(this) override fun toString(): String = diff --git a/phoebe-java-core/src/test/kotlin/com/phoebe/api/core/http/HttpRequestTest.kt b/phoebe-java-core/src/test/kotlin/com/phoebe/api/core/http/HttpRequestTest.kt new file mode 100644 index 0000000..063a971 --- /dev/null +++ b/phoebe-java-core/src/test/kotlin/com/phoebe/api/core/http/HttpRequestTest.kt @@ -0,0 +1,110 @@ +package com.phoebe.api.core.http + +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.EnumSource + +internal class HttpRequestTest { + + enum class UrlTestCase(val request: HttpRequest, val expectedUrl: String) { + BASE_URL_ONLY( + HttpRequest.builder().method(HttpMethod.GET).baseUrl("https://api.example.com").build(), + expectedUrl = "https://api.example.com", + ), + BASE_URL_WITH_TRAILING_SLASH( + HttpRequest.builder() + .method(HttpMethod.GET) + .baseUrl("https://api.example.com/") + .build(), + expectedUrl = "https://api.example.com/", + ), + SINGLE_PATH_SEGMENT( + HttpRequest.builder() + .method(HttpMethod.GET) + .baseUrl("https://api.example.com") + .addPathSegment("users") + .build(), + expectedUrl = "https://api.example.com/users", + ), + MULTIPLE_PATH_SEGMENTS( + HttpRequest.builder() + .method(HttpMethod.GET) + .baseUrl("https://api.example.com") + .addPathSegments("users", "123", "profile") + .build(), + expectedUrl = "https://api.example.com/users/123/profile", + ), + PATH_SEGMENT_WITH_SPECIAL_CHARS( + HttpRequest.builder() + .method(HttpMethod.GET) + .baseUrl("https://api.example.com") + .addPathSegment("user name") + .build(), + expectedUrl = "https://api.example.com/user+name", + ), + SINGLE_QUERY_PARAM( + HttpRequest.builder() + .method(HttpMethod.GET) + .baseUrl("https://api.example.com") + .addPathSegment("users") + .putQueryParam("limit", "10") + .build(), + expectedUrl = "https://api.example.com/users?limit=10", + ), + MULTIPLE_QUERY_PARAMS( + HttpRequest.builder() + .method(HttpMethod.GET) + .baseUrl("https://api.example.com") + .addPathSegment("users") + .putQueryParam("limit", "10") + .putQueryParam("offset", "20") + .build(), + expectedUrl = "https://api.example.com/users?limit=10&offset=20", + ), + QUERY_PARAM_WITH_SPECIAL_CHARS( + HttpRequest.builder() + .method(HttpMethod.GET) + .baseUrl("https://api.example.com") + .addPathSegment("search") + .putQueryParam("q", "hello world") + .build(), + expectedUrl = "https://api.example.com/search?q=hello+world", + ), + MULTIPLE_VALUES_SAME_PARAM( + HttpRequest.builder() + .method(HttpMethod.GET) + .baseUrl("https://api.example.com") + .addPathSegment("users") + .putQueryParams("tags", listOf("admin", "user")) + .build(), + expectedUrl = "https://api.example.com/users?tags=admin&tags=user", + ), + BASE_URL_WITH_TRAILING_SLASH_AND_PATH( + HttpRequest.builder() + .method(HttpMethod.GET) + .baseUrl("https://api.example.com/") + .addPathSegment("users") + .build(), + expectedUrl = "https://api.example.com/users", + ), + COMPLEX_URL( + HttpRequest.builder() + .method(HttpMethod.POST) + .baseUrl("https://api.example.com") + .addPathSegments("v1", "users", "123") + .putQueryParams("include", listOf("profile", "settings")) + .putQueryParam("format", "json") + .build(), + expectedUrl = + "https://api.example.com/v1/users/123?include=profile&include=settings&format=json", + ), + } + + @ParameterizedTest + @EnumSource + fun url(testCase: UrlTestCase) { + val actualUrl = testCase.request.url() + + assertThat(actualUrl).isEqualTo(testCase.expectedUrl) + } +} From 7754ce1495022f309ff26d5c2f06f3c4c8e913b7 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 6 Jan 2026 07:35:41 +0000 Subject: [PATCH 07/25] docs: prominently feature MCP server setup in root SDK readmes --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index 8cffb72..7aaccd5 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,15 @@ The Phoebe Java SDK provides convenient access to the [Phoebe REST API](https:// It is generated with [Stainless](https://www.stainless.com/). +## MCP Server + +Use the Phoebe MCP Server to enable AI assistants to interact with this API, allowing them to explore endpoints, make test requests, and use documentation to help integrate this SDK into your application. + +[![Add to Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en-US/install-mcp?name=phoebe-ebird-mcp&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsInBob2ViZS1lYmlyZC1tY3AiXX0) +[![Install in VS Code](https://img.shields.io/badge/_-Add_to_VS_Code-blue?style=for-the-badge&logo=data:image/svg%2bxml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9Im5vbmUiIHZpZXdCb3g9IjAgMCA0MCA0MCI+PHBhdGggZmlsbD0iI0VFRSIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMzAuMjM1IDM5Ljg4NGEyLjQ5MSAyLjQ5MSAwIDAgMS0xLjc4MS0uNzNMMTIuNyAyNC43OGwtMy40NiAyLjYyNC0zLjQwNiAyLjU4MmExLjY2NSAxLjY2NSAwIDAgMS0xLjA4Mi4zMzggMS42NjQgMS42NjQgMCAwIDEtMS4wNDYtLjQzMWwtMi4yLTJhMS42NjYgMS42NjYgMCAwIDEgMC0yLjQ2M0w3LjQ1OCAyMCA0LjY3IDE3LjQ1MyAxLjUwNyAxNC41N2ExLjY2NSAxLjY2NSAwIDAgMSAwLTIuNDYzbDIuMi0yYTEuNjY1IDEuNjY1IDAgMCAxIDIuMTMtLjA5N2w2Ljg2MyA1LjIwOUwyOC40NTIuODQ0YTIuNDg4IDIuNDg4IDAgMCAxIDEuODQxLS43MjljLjM1MS4wMDkuNjk5LjA5MSAxLjAxOS4yNDVsOC4yMzYgMy45NjFhMi41IDIuNSAwIDAgMSAxLjQxNSAyLjI1M3YuMDk5LS4wNDVWMzMuMzd2LS4wNDUuMDk1YTIuNTAxIDIuNTAxIDAgMCAxLTEuNDE2IDIuMjU3bC04LjIzNSAzLjk2MWEyLjQ5MiAyLjQ5MiAwIDAgMS0xLjA3Ny4yNDZabS43MTYtMjguOTQ3LTExLjk0OCA5LjA2MiAxMS45NTIgOS4wNjUtLjAwNC0xOC4xMjdaIi8+PC9zdmc+)](https://vscode.stainless.com/mcp/%7B%22name%22%3A%22phoebe-ebird-mcp%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22phoebe-ebird-mcp%22%5D%7D) + +> Note: You may need to set environment variables in your MCP client. + The REST API documentation can be found on [science.ebird.org](https://science.ebird.org/en/use-ebird-data/download-ebird-data-products). Javadocs are available on [javadoc.io](https://javadoc.io/doc/com.phoebe.api/phoebe-java/0.0.1-alpha.0). From ef7c2660841ba59c269caa78b58f9a9f59fa162c Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 9 Jan 2026 06:03:19 +0000 Subject: [PATCH 08/25] feat(client): allow configuring dispatcher executor service --- .../phoebe/api/client/okhttp/OkHttpClient.kt | 9 ++++++++ .../api/client/okhttp/PhoebeOkHttpClient.kt | 22 +++++++++++++++++++ .../client/okhttp/PhoebeOkHttpClientAsync.kt | 22 +++++++++++++++++++ 3 files changed, 53 insertions(+) diff --git a/phoebe-java-client-okhttp/src/main/kotlin/com/phoebe/api/client/okhttp/OkHttpClient.kt b/phoebe-java-client-okhttp/src/main/kotlin/com/phoebe/api/client/okhttp/OkHttpClient.kt index 32c9047..b52aa5b 100644 --- a/phoebe-java-client-okhttp/src/main/kotlin/com/phoebe/api/client/okhttp/OkHttpClient.kt +++ b/phoebe-java-client-okhttp/src/main/kotlin/com/phoebe/api/client/okhttp/OkHttpClient.kt @@ -15,11 +15,13 @@ import java.net.Proxy import java.time.Duration import java.util.concurrent.CancellationException import java.util.concurrent.CompletableFuture +import java.util.concurrent.ExecutorService import javax.net.ssl.HostnameVerifier import javax.net.ssl.SSLSocketFactory import javax.net.ssl.X509TrustManager import okhttp3.Call import okhttp3.Callback +import okhttp3.Dispatcher import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.MediaType import okhttp3.MediaType.Companion.toMediaType @@ -200,6 +202,7 @@ private constructor(@JvmSynthetic internal val okHttpClient: okhttp3.OkHttpClien private var timeout: Timeout = Timeout.default() private var proxy: Proxy? = null + private var dispatcherExecutorService: ExecutorService? = null private var sslSocketFactory: SSLSocketFactory? = null private var trustManager: X509TrustManager? = null private var hostnameVerifier: HostnameVerifier? = null @@ -210,6 +213,10 @@ private constructor(@JvmSynthetic internal val okHttpClient: okhttp3.OkHttpClien fun proxy(proxy: Proxy?) = apply { this.proxy = proxy } + fun dispatcherExecutorService(dispatcherExecutorService: ExecutorService?) = apply { + this.dispatcherExecutorService = dispatcherExecutorService + } + fun sslSocketFactory(sslSocketFactory: SSLSocketFactory?) = apply { this.sslSocketFactory = sslSocketFactory } @@ -231,6 +238,8 @@ private constructor(@JvmSynthetic internal val okHttpClient: okhttp3.OkHttpClien .callTimeout(timeout.request()) .proxy(proxy) .apply { + dispatcherExecutorService?.let { dispatcher(Dispatcher(it)) } + val sslSocketFactory = sslSocketFactory val trustManager = trustManager if (sslSocketFactory != null && trustManager != null) { diff --git a/phoebe-java-client-okhttp/src/main/kotlin/com/phoebe/api/client/okhttp/PhoebeOkHttpClient.kt b/phoebe-java-client-okhttp/src/main/kotlin/com/phoebe/api/client/okhttp/PhoebeOkHttpClient.kt index 57aa49e..7e9e5ad 100644 --- a/phoebe-java-client-okhttp/src/main/kotlin/com/phoebe/api/client/okhttp/PhoebeOkHttpClient.kt +++ b/phoebe-java-client-okhttp/src/main/kotlin/com/phoebe/api/client/okhttp/PhoebeOkHttpClient.kt @@ -16,6 +16,7 @@ import java.net.Proxy import java.time.Clock import java.time.Duration import java.util.Optional +import java.util.concurrent.ExecutorService import javax.net.ssl.HostnameVerifier import javax.net.ssl.SSLSocketFactory import javax.net.ssl.X509TrustManager @@ -44,11 +45,31 @@ class PhoebeOkHttpClient private constructor() { class Builder internal constructor() { private var clientOptions: ClientOptions.Builder = ClientOptions.builder() + private var dispatcherExecutorService: ExecutorService? = null private var proxy: Proxy? = null private var sslSocketFactory: SSLSocketFactory? = null private var trustManager: X509TrustManager? = null private var hostnameVerifier: HostnameVerifier? = null + /** + * The executor service to use for running HTTP requests. + * + * Defaults to OkHttp's + * [default executor service](https://github.com/square/okhttp/blob/ace792f443b2ffb17974f5c0d1cecdf589309f26/okhttp/src/commonJvmAndroid/kotlin/okhttp3/Dispatcher.kt#L98-L104). + * + * This class takes ownership of the executor service and shuts it down when closed. + */ + fun dispatcherExecutorService(dispatcherExecutorService: ExecutorService?) = apply { + this.dispatcherExecutorService = dispatcherExecutorService + } + + /** + * Alias for calling [Builder.dispatcherExecutorService] with + * `dispatcherExecutorService.orElse(null)`. + */ + fun dispatcherExecutorService(dispatcherExecutorService: Optional) = + dispatcherExecutorService(dispatcherExecutorService.getOrNull()) + fun proxy(proxy: Proxy?) = apply { this.proxy = proxy } /** Alias for calling [Builder.proxy] with `proxy.orElse(null)`. */ @@ -296,6 +317,7 @@ class PhoebeOkHttpClient private constructor() { OkHttpClient.builder() .timeout(clientOptions.timeout()) .proxy(proxy) + .dispatcherExecutorService(dispatcherExecutorService) .sslSocketFactory(sslSocketFactory) .trustManager(trustManager) .hostnameVerifier(hostnameVerifier) diff --git a/phoebe-java-client-okhttp/src/main/kotlin/com/phoebe/api/client/okhttp/PhoebeOkHttpClientAsync.kt b/phoebe-java-client-okhttp/src/main/kotlin/com/phoebe/api/client/okhttp/PhoebeOkHttpClientAsync.kt index e133879..4fba04c 100644 --- a/phoebe-java-client-okhttp/src/main/kotlin/com/phoebe/api/client/okhttp/PhoebeOkHttpClientAsync.kt +++ b/phoebe-java-client-okhttp/src/main/kotlin/com/phoebe/api/client/okhttp/PhoebeOkHttpClientAsync.kt @@ -16,6 +16,7 @@ import java.net.Proxy import java.time.Clock import java.time.Duration import java.util.Optional +import java.util.concurrent.ExecutorService import javax.net.ssl.HostnameVerifier import javax.net.ssl.SSLSocketFactory import javax.net.ssl.X509TrustManager @@ -44,11 +45,31 @@ class PhoebeOkHttpClientAsync private constructor() { class Builder internal constructor() { private var clientOptions: ClientOptions.Builder = ClientOptions.builder() + private var dispatcherExecutorService: ExecutorService? = null private var proxy: Proxy? = null private var sslSocketFactory: SSLSocketFactory? = null private var trustManager: X509TrustManager? = null private var hostnameVerifier: HostnameVerifier? = null + /** + * The executor service to use for running HTTP requests. + * + * Defaults to OkHttp's + * [default executor service](https://github.com/square/okhttp/blob/ace792f443b2ffb17974f5c0d1cecdf589309f26/okhttp/src/commonJvmAndroid/kotlin/okhttp3/Dispatcher.kt#L98-L104). + * + * This class takes ownership of the executor service and shuts it down when closed. + */ + fun dispatcherExecutorService(dispatcherExecutorService: ExecutorService?) = apply { + this.dispatcherExecutorService = dispatcherExecutorService + } + + /** + * Alias for calling [Builder.dispatcherExecutorService] with + * `dispatcherExecutorService.orElse(null)`. + */ + fun dispatcherExecutorService(dispatcherExecutorService: Optional) = + dispatcherExecutorService(dispatcherExecutorService.getOrNull()) + fun proxy(proxy: Proxy?) = apply { this.proxy = proxy } /** Alias for calling [Builder.proxy] with `proxy.orElse(null)`. */ @@ -296,6 +317,7 @@ class PhoebeOkHttpClientAsync private constructor() { OkHttpClient.builder() .timeout(clientOptions.timeout()) .proxy(proxy) + .dispatcherExecutorService(dispatcherExecutorService) .sslSocketFactory(sslSocketFactory) .trustManager(trustManager) .hostnameVerifier(hostnameVerifier) From 37c89f8c816525461bce82b5550d796ff1ff3023 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 13 Jan 2026 12:21:41 +0000 Subject: [PATCH 09/25] chore(internal): support uploading Maven repo artifacts to stainless package server --- .github/workflows/ci.yml | 18 ++++ .../src/main/kotlin/phoebe.publish.gradle.kts | 8 ++ scripts/upload-artifacts | 96 +++++++++++++++++++ 3 files changed, 122 insertions(+) create mode 100755 scripts/upload-artifacts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6d4e261..4abc195 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -40,6 +40,9 @@ jobs: build: timeout-minutes: 15 name: build + permissions: + contents: read + id-token: write runs-on: ${{ github.repository == 'stainless-sdks/phoebe-java' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} if: github.event_name == 'push' || github.event.pull_request.head.repo.fork @@ -61,6 +64,21 @@ jobs: - name: Build SDK run: ./scripts/build + - name: Get GitHub OIDC Token + if: github.repository == 'stainless-sdks/phoebe-java' + id: github-oidc + uses: actions/github-script@v6 + with: + script: core.setOutput('github_token', await core.getIDToken()); + + - name: Build and upload Maven artifacts + if: github.repository == 'stainless-sdks/phoebe-java' + env: + URL: https://pkg.stainless.com/s + AUTH: ${{ steps.github-oidc.outputs.github_token }} + SHA: ${{ github.sha }} + PROJECT: phoebe-java + run: ./scripts/upload-artifacts test: timeout-minutes: 15 name: test diff --git a/buildSrc/src/main/kotlin/phoebe.publish.gradle.kts b/buildSrc/src/main/kotlin/phoebe.publish.gradle.kts index 3bf789a..a6386d7 100644 --- a/buildSrc/src/main/kotlin/phoebe.publish.gradle.kts +++ b/buildSrc/src/main/kotlin/phoebe.publish.gradle.kts @@ -40,6 +40,14 @@ configure { } } } + repositories { + if (project.hasProperty("publishLocal")) { + maven { + name = "LocalFileSystem" + url = uri("${rootProject.layout.buildDirectory.get()}/local-maven-repo") + } + } + } } signing { diff --git a/scripts/upload-artifacts b/scripts/upload-artifacts new file mode 100755 index 0000000..729e6f2 --- /dev/null +++ b/scripts/upload-artifacts @@ -0,0 +1,96 @@ +#!/usr/bin/env bash + +set -euo pipefail + +# ANSI Color Codes +GREEN='\033[32m' +RED='\033[31m' +NC='\033[0m' # No Color + +log_error() { + local msg="$1" + local headers="$2" + local body="$3" + echo -e "${RED}${msg}${NC}" + [[ -f "$headers" ]] && echo -e "${RED}Headers:$(cat "$headers")${NC}" + echo -e "${RED}Body: ${body}${NC}" + exit 1 +} + +upload_file() { + local file_name="$1" + local tmp_headers + tmp_headers=$(mktemp) + + if [ -f "$file_name" ]; then + echo -e "${GREEN}Processing file: $file_name${NC}" + pkg_file_name="mvn${file_name#./build/local-maven-repo}" + + # Get signed URL for uploading artifact file + signed_url_response=$(curl -X POST -G "$URL" \ + -sS --retry 5 \ + -D "$tmp_headers" \ + --data-urlencode "filename=$pkg_file_name" \ + -H "Authorization: Bearer $AUTH" \ + -H "Content-Type: application/json") + + # Validate JSON and extract URL + if ! signed_url=$(echo "$signed_url_response" | jq -e -r '.url' 2>/dev/null) || [[ "$signed_url" == "null" ]]; then + log_error "Failed to get valid signed URL" "$tmp_headers" "$signed_url_response" + fi + + # Set content-type based on file extension + local extension="${file_name##*.}" + local content_type + case "$extension" in + jar) content_type="application/java-archive" ;; + md5|sha1|sha256|sha512) content_type="text/plain" ;; + module) content_type="application/json" ;; + pom|xml) content_type="application/xml" ;; + *) content_type="application/octet-stream" ;; + esac + + # Upload file + upload_response=$(curl -v -X PUT \ + --retry 5 \ + -D "$tmp_headers" \ + -H "Content-Type: $content_type" \ + --data-binary "@${file_name}" "$signed_url" 2>&1) + + if ! echo "$upload_response" | grep -q "HTTP/[0-9.]* 200"; then + log_error "Failed upload artifact file" "$tmp_headers" "$upload_response" + fi + + # Insert small throttle to reduce rate limiting risk + sleep 0.1 + fi +} + +walk_tree() { + local current_dir="$1" + + for entry in "$current_dir"/*; do + # Check that entry is valid + [ -e "$entry" ] || [ -h "$entry" ] || continue + + if [ -d "$entry" ]; then + walk_tree "$entry" + else + upload_file "$entry" + fi + done +} + +cd "$(dirname "$0")/.." + +echo "::group::Creating local Maven content" +./gradlew publishMavenPublicationToLocalFileSystemRepository -PpublishLocal +echo "::endgroup::" + +echo "::group::Uploading to pkg.stainless.com" +walk_tree "./build/local-maven-repo" +echo "::endgroup::" + +echo "::group::Generating instructions" +echo "Configure maven or gradle to use the repo located at 'https://pkg.stainless.com/s/${PROJECT}/${SHA}/mvn'" +echo "::endgroup::" From 6a0f2cab131804296e2b29cc2e29c58de3603a2b Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 16 Jan 2026 05:18:33 +0000 Subject: [PATCH 10/25] chore(internal): clean up maven repo artifact script and add html documentation to repo root --- scripts/upload-artifacts | 44 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 41 insertions(+), 3 deletions(-) diff --git a/scripts/upload-artifacts b/scripts/upload-artifacts index 729e6f2..df0c8d9 100755 --- a/scripts/upload-artifacts +++ b/scripts/upload-artifacts @@ -7,6 +7,8 @@ GREEN='\033[32m' RED='\033[31m' NC='\033[0m' # No Color +MAVEN_REPO_PATH="./build/local-maven-repo" + log_error() { local msg="$1" local headers="$2" @@ -24,7 +26,7 @@ upload_file() { if [ -f "$file_name" ]; then echo -e "${GREEN}Processing file: $file_name${NC}" - pkg_file_name="mvn${file_name#./build/local-maven-repo}" + pkg_file_name="mvn${file_name#"${MAVEN_REPO_PATH}"}" # Get signed URL for uploading artifact file signed_url_response=$(curl -X POST -G "$URL" \ @@ -47,6 +49,7 @@ upload_file() { md5|sha1|sha256|sha512) content_type="text/plain" ;; module) content_type="application/json" ;; pom|xml) content_type="application/xml" ;; + html) content_type="text/html" ;; *) content_type="application/octet-stream" ;; esac @@ -81,6 +84,41 @@ walk_tree() { done } +generate_instructions() { + cat << EOF > "$MAVEN_REPO_PATH/index.html" + + + + Maven Repo + + +

Stainless SDK Maven Repository

+

This is the Maven repository for your Stainless Java SDK build.

+ +

Directions

+

To use the uploaded Maven repository, add the following to your project's pom.xml:

+
<repositories>
+    <repository>
+        <id>stainless-sdk-repo</id>
+        <url>https://pkg.stainless.com/s/${PROJECT}/${SHA}/mvn</url>
+    </repository>
+</repositories>
+ +

If you're using Gradle, add the following to your build.gradle file:

+
repositories {
+    maven {
+        url 'https://pkg.stainless.com/s/${PROJECT}/${SHA}/mvn'
+    }
+}
+ + +EOF + upload_file "${MAVEN_REPO_PATH}/index.html" + + echo "Configure maven or gradle to use the repo located at 'https://pkg.stainless.com/s/${PROJECT}/${SHA}/mvn'" + echo "For more details, see the directions in https://pkg.stainless.com/s/${PROJECT}/${SHA}/mvn/index.html" +} + cd "$(dirname "$0")/.." echo "::group::Creating local Maven content" @@ -88,9 +126,9 @@ echo "::group::Creating local Maven content" echo "::endgroup::" echo "::group::Uploading to pkg.stainless.com" -walk_tree "./build/local-maven-repo" +walk_tree "$MAVEN_REPO_PATH" echo "::endgroup::" echo "::group::Generating instructions" -echo "Configure maven or gradle to use the repo located at 'https://pkg.stainless.com/s/${PROJECT}/${SHA}/mvn'" +generate_instructions echo "::endgroup::" From 9d5f042b85f9bdaec3a56ad6ea180a32f6a5f454 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 17 Jan 2026 07:44:48 +0000 Subject: [PATCH 11/25] chore: test on Jackson 2.14.0 to avoid encountering FasterXML/jackson-databind#3240 in tests fix: date time deserialization leniency --- README.md | 2 ++ phoebe-java-core/build.gradle.kts | 18 +++++----- .../com/phoebe/api/core/ObjectMappers.kt | 33 ++++++++++++------- .../com/phoebe/api/core/ObjectMappersTest.kt | 16 +++------ phoebe-java-proguard-test/build.gradle.kts | 2 +- 5 files changed, 38 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index 7aaccd5..5027fa2 100644 --- a/README.md +++ b/README.md @@ -267,6 +267,8 @@ If the SDK threw an exception, but you're _certain_ the version is compatible, t > [!CAUTION] > We make no guarantee that the SDK works correctly when the Jackson version check is disabled. +Also note that there are bugs in older Jackson versions that can affect the SDK. We don't work around all Jackson bugs ([example](https://github.com/FasterXML/jackson-databind/issues/3240)) and expect users to upgrade Jackson for those instead. + ## Network options ### Retries diff --git a/phoebe-java-core/build.gradle.kts b/phoebe-java-core/build.gradle.kts index a65d57c..bf0d85a 100644 --- a/phoebe-java-core/build.gradle.kts +++ b/phoebe-java-core/build.gradle.kts @@ -5,14 +5,16 @@ plugins { configurations.all { resolutionStrategy { - // Compile and test against a lower Jackson version to ensure we're compatible with it. - // We publish with a higher version (see below) to ensure users depend on a secure version by default. - force("com.fasterxml.jackson.core:jackson-core:2.13.4") - force("com.fasterxml.jackson.core:jackson-databind:2.13.4") - force("com.fasterxml.jackson.core:jackson-annotations:2.13.4") - force("com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.13.4") - force("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.13.4") - force("com.fasterxml.jackson.module:jackson-module-kotlin:2.13.4") + // Compile and test against a lower Jackson version to ensure we're compatible with it. Note that + // we generally support 2.13.4, but test against 2.14.0 because 2.13.4 has some annoying (but + // niche) bugs (users should upgrade if they encounter them). We publish with a higher version + // (see below) to ensure users depend on a secure version by default. + force("com.fasterxml.jackson.core:jackson-core:2.14.0") + force("com.fasterxml.jackson.core:jackson-databind:2.14.0") + force("com.fasterxml.jackson.core:jackson-annotations:2.14.0") + force("com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.14.0") + force("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.14.0") + force("com.fasterxml.jackson.module:jackson-module-kotlin:2.14.0") } } diff --git a/phoebe-java-core/src/main/kotlin/com/phoebe/api/core/ObjectMappers.kt b/phoebe-java-core/src/main/kotlin/com/phoebe/api/core/ObjectMappers.kt index 8c8d7e2..01f983d 100644 --- a/phoebe-java-core/src/main/kotlin/com/phoebe/api/core/ObjectMappers.kt +++ b/phoebe-java-core/src/main/kotlin/com/phoebe/api/core/ObjectMappers.kt @@ -24,6 +24,7 @@ import java.io.InputStream import java.time.DateTimeException import java.time.LocalDate import java.time.LocalDateTime +import java.time.OffsetDateTime import java.time.ZonedDateTime import java.time.format.DateTimeFormatter import java.time.temporal.ChronoField @@ -36,7 +37,7 @@ fun jsonMapper(): JsonMapper = .addModule( SimpleModule() .addSerializer(InputStreamSerializer) - .addDeserializer(LocalDateTime::class.java, LenientLocalDateTimeDeserializer()) + .addDeserializer(OffsetDateTime::class.java, LenientOffsetDateTimeDeserializer()) ) .withCoercionConfig(LogicalType.Boolean) { it.setCoercion(CoercionInputShape.Integer, CoercionAction.Fail) @@ -64,6 +65,12 @@ fun jsonMapper(): JsonMapper = .setCoercion(CoercionInputShape.Array, CoercionAction.Fail) .setCoercion(CoercionInputShape.Object, CoercionAction.Fail) } + .withCoercionConfig(LogicalType.DateTime) { + it.setCoercion(CoercionInputShape.Integer, CoercionAction.Fail) + .setCoercion(CoercionInputShape.Float, CoercionAction.Fail) + .setCoercion(CoercionInputShape.Array, CoercionAction.Fail) + .setCoercion(CoercionInputShape.Object, CoercionAction.Fail) + } .withCoercionConfig(LogicalType.Array) { it.setCoercion(CoercionInputShape.Boolean, CoercionAction.Fail) .setCoercion(CoercionInputShape.Integer, CoercionAction.Fail) @@ -124,10 +131,10 @@ private object InputStreamSerializer : BaseSerializer(InputStream:: } /** - * A deserializer that can deserialize [LocalDateTime] from datetimes, dates, and zoned datetimes. + * A deserializer that can deserialize [OffsetDateTime] from datetimes, dates, and zoned datetimes. */ -private class LenientLocalDateTimeDeserializer : - StdDeserializer(LocalDateTime::class.java) { +private class LenientOffsetDateTimeDeserializer : + StdDeserializer(OffsetDateTime::class.java) { companion object { @@ -141,7 +148,7 @@ private class LenientLocalDateTimeDeserializer : override fun logicalType(): LogicalType = LogicalType.DateTime - override fun deserialize(p: JsonParser, context: DeserializationContext?): LocalDateTime { + override fun deserialize(p: JsonParser, context: DeserializationContext): OffsetDateTime { val exceptions = mutableListOf() for (formatter in DATE_TIME_FORMATTERS) { @@ -149,18 +156,20 @@ private class LenientLocalDateTimeDeserializer : val temporal = formatter.parse(p.text) return when { - !temporal.isSupported(ChronoField.HOUR_OF_DAY) -> - LocalDate.from(temporal).atStartOfDay() - !temporal.isSupported(ChronoField.OFFSET_SECONDS) -> - LocalDateTime.from(temporal) - else -> ZonedDateTime.from(temporal).toLocalDateTime() - } + !temporal.isSupported(ChronoField.HOUR_OF_DAY) -> + LocalDate.from(temporal).atStartOfDay() + !temporal.isSupported(ChronoField.OFFSET_SECONDS) -> + LocalDateTime.from(temporal) + else -> ZonedDateTime.from(temporal).toLocalDateTime() + } + .atZone(context.timeZone.toZoneId()) + .toOffsetDateTime() } catch (e: DateTimeException) { exceptions.add(e) } } - throw JsonParseException(p, "Cannot parse `LocalDateTime` from value: ${p.text}").apply { + throw JsonParseException(p, "Cannot parse `OffsetDateTime` from value: ${p.text}").apply { exceptions.forEach { addSuppressed(it) } } } diff --git a/phoebe-java-core/src/test/kotlin/com/phoebe/api/core/ObjectMappersTest.kt b/phoebe-java-core/src/test/kotlin/com/phoebe/api/core/ObjectMappersTest.kt index 35ca1ac..4979954 100644 --- a/phoebe-java-core/src/test/kotlin/com/phoebe/api/core/ObjectMappersTest.kt +++ b/phoebe-java-core/src/test/kotlin/com/phoebe/api/core/ObjectMappersTest.kt @@ -3,7 +3,7 @@ package com.phoebe.api.core import com.fasterxml.jackson.annotation.JsonProperty import com.fasterxml.jackson.databind.exc.MismatchedInputException import com.fasterxml.jackson.module.kotlin.readValue -import java.time.LocalDateTime +import java.time.OffsetDateTime import kotlin.reflect.KClass import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.catchThrowable @@ -58,14 +58,6 @@ internal class ObjectMappersTest { LONG to DOUBLE, LONG to INTEGER, CLASS to MAP, - // These aren't actually valid, but coercion configs don't work for String until - // v2.14.0: https://github.com/FasterXML/jackson-databind/issues/3240 - // We currently test on v2.13.4. - BOOLEAN to STRING, - FLOAT to STRING, - DOUBLE to STRING, - INTEGER to STRING, - LONG to STRING, ) } } @@ -84,7 +76,7 @@ internal class ObjectMappersTest { } } - enum class LenientLocalDateTimeTestCase(val string: String) { + enum class LenientOffsetDateTimeTestCase(val string: String) { DATE("1998-04-21"), DATE_TIME("1998-04-21T04:00:00"), ZONED_DATE_TIME_1("1998-04-21T04:00:00+03:00"), @@ -93,10 +85,10 @@ internal class ObjectMappersTest { @ParameterizedTest @EnumSource - fun readLocalDateTime_lenient(testCase: LenientLocalDateTimeTestCase) { + fun readOffsetDateTime_lenient(testCase: LenientOffsetDateTimeTestCase) { val jsonMapper = jsonMapper() val json = jsonMapper.writeValueAsString(testCase.string) - assertDoesNotThrow { jsonMapper().readValue(json) } + assertDoesNotThrow { jsonMapper().readValue(json) } } } diff --git a/phoebe-java-proguard-test/build.gradle.kts b/phoebe-java-proguard-test/build.gradle.kts index 932d96a..7bcadd4 100644 --- a/phoebe-java-proguard-test/build.gradle.kts +++ b/phoebe-java-proguard-test/build.gradle.kts @@ -19,7 +19,7 @@ dependencies { testImplementation(kotlin("test")) testImplementation("org.junit.jupiter:junit-jupiter-api:5.9.3") testImplementation("org.assertj:assertj-core:3.25.3") - testImplementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.13.4") + testImplementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.14.0") } tasks.shadowJar { From da49cb77739c9becf5d45b12095298950f1e32f9 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 17 Jan 2026 07:48:12 +0000 Subject: [PATCH 12/25] chore(internal): improve maven repo docs --- scripts/upload-artifacts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/scripts/upload-artifacts b/scripts/upload-artifacts index df0c8d9..548d152 100755 --- a/scripts/upload-artifacts +++ b/scripts/upload-artifacts @@ -56,12 +56,13 @@ upload_file() { # Upload file upload_response=$(curl -v -X PUT \ --retry 5 \ + --retry-all-errors \ -D "$tmp_headers" \ -H "Content-Type: $content_type" \ --data-binary "@${file_name}" "$signed_url" 2>&1) if ! echo "$upload_response" | grep -q "HTTP/[0-9.]* 200"; then - log_error "Failed upload artifact file" "$tmp_headers" "$upload_response" + log_error "Failed to upload artifact file" "$tmp_headers" "$upload_response" fi # Insert small throttle to reduce rate limiting risk @@ -110,6 +111,10 @@ generate_instructions() { url 'https://pkg.stainless.com/s/${PROJECT}/${SHA}/mvn' } } + +

Once you've added the repository, you can include dependencies from it as usual. See your + project README + for more details.

EOF From d00944fd323009658808104c8a4b3954211b3559 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 17 Jan 2026 07:54:10 +0000 Subject: [PATCH 13/25] fix(client): disallow coercion from float to int --- .../src/main/kotlin/com/phoebe/api/core/ObjectMappers.kt | 1 + .../src/test/kotlin/com/phoebe/api/core/ObjectMappersTest.kt | 4 ---- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/phoebe-java-core/src/main/kotlin/com/phoebe/api/core/ObjectMappers.kt b/phoebe-java-core/src/main/kotlin/com/phoebe/api/core/ObjectMappers.kt index 01f983d..d69243e 100644 --- a/phoebe-java-core/src/main/kotlin/com/phoebe/api/core/ObjectMappers.kt +++ b/phoebe-java-core/src/main/kotlin/com/phoebe/api/core/ObjectMappers.kt @@ -48,6 +48,7 @@ fun jsonMapper(): JsonMapper = } .withCoercionConfig(LogicalType.Integer) { it.setCoercion(CoercionInputShape.Boolean, CoercionAction.Fail) + .setCoercion(CoercionInputShape.Float, CoercionAction.Fail) .setCoercion(CoercionInputShape.String, CoercionAction.Fail) .setCoercion(CoercionInputShape.Array, CoercionAction.Fail) .setCoercion(CoercionInputShape.Object, CoercionAction.Fail) diff --git a/phoebe-java-core/src/test/kotlin/com/phoebe/api/core/ObjectMappersTest.kt b/phoebe-java-core/src/test/kotlin/com/phoebe/api/core/ObjectMappersTest.kt index 4979954..ae0747a 100644 --- a/phoebe-java-core/src/test/kotlin/com/phoebe/api/core/ObjectMappersTest.kt +++ b/phoebe-java-core/src/test/kotlin/com/phoebe/api/core/ObjectMappersTest.kt @@ -46,11 +46,7 @@ internal class ObjectMappersTest { val VALID_CONVERSIONS = listOf( FLOAT to DOUBLE, - FLOAT to INTEGER, - FLOAT to LONG, DOUBLE to FLOAT, - DOUBLE to INTEGER, - DOUBLE to LONG, INTEGER to FLOAT, INTEGER to DOUBLE, INTEGER to LONG, From 3a146bbdb3ab84f558391f3b8e6bc7e2c8fd77f6 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 17 Jan 2026 07:55:19 +0000 Subject: [PATCH 14/25] chore(internal): update `actions/checkout` version --- .github/workflows/ci.yml | 6 +++--- .github/workflows/publish-sonatype.yml | 2 +- .github/workflows/release-doctor.yml | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4abc195..ffa2c80 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,7 +20,7 @@ jobs: if: github.event_name == 'push' || github.event.pull_request.head.repo.fork steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Set up Java uses: actions/setup-java@v4 @@ -47,7 +47,7 @@ jobs: if: github.event_name == 'push' || github.event.pull_request.head.repo.fork steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Set up Java uses: actions/setup-java@v4 @@ -85,7 +85,7 @@ jobs: runs-on: ${{ github.repository == 'stainless-sdks/phoebe-java' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} if: github.event_name == 'push' || github.event.pull_request.head.repo.fork steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Set up Java uses: actions/setup-java@v4 diff --git a/.github/workflows/publish-sonatype.yml b/.github/workflows/publish-sonatype.yml index 48cce0b..1730207 100644 --- a/.github/workflows/publish-sonatype.yml +++ b/.github/workflows/publish-sonatype.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Set up Java uses: actions/setup-java@v4 diff --git a/.github/workflows/release-doctor.yml b/.github/workflows/release-doctor.yml index e220472..3791b5c 100644 --- a/.github/workflows/release-doctor.yml +++ b/.github/workflows/release-doctor.yml @@ -12,7 +12,7 @@ jobs: if: github.repository == 'phoebe-bird/phoebe-java' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || startsWith(github.head_ref, 'release-please') || github.head_ref == 'next') steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Check release environment run: | From 7c71f9dc52f85d1a055e25f5d940ac1fc6d807b0 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 17 Jan 2026 07:57:45 +0000 Subject: [PATCH 15/25] fix(client): fully respect max retries fix(client): send retry count header for max retries 0 chore(internal): depend on packages directly in example --- .../phoebe/api/client/okhttp/OkHttpClient.kt | 2 ++ .../api/core/http/RetryingHttpClient.kt | 20 +++++++++---------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/phoebe-java-client-okhttp/src/main/kotlin/com/phoebe/api/client/okhttp/OkHttpClient.kt b/phoebe-java-client-okhttp/src/main/kotlin/com/phoebe/api/client/okhttp/OkHttpClient.kt index b52aa5b..6cc8418 100644 --- a/phoebe-java-client-okhttp/src/main/kotlin/com/phoebe/api/client/okhttp/OkHttpClient.kt +++ b/phoebe-java-client-okhttp/src/main/kotlin/com/phoebe/api/client/okhttp/OkHttpClient.kt @@ -232,6 +232,8 @@ private constructor(@JvmSynthetic internal val okHttpClient: okhttp3.OkHttpClien fun build(): OkHttpClient = OkHttpClient( okhttp3.OkHttpClient.Builder() + // `RetryingHttpClient` handles retries if the user enabled them. + .retryOnConnectionFailure(false) .connectTimeout(timeout.connect()) .readTimeout(timeout.read()) .writeTimeout(timeout.write()) diff --git a/phoebe-java-core/src/main/kotlin/com/phoebe/api/core/http/RetryingHttpClient.kt b/phoebe-java-core/src/main/kotlin/com/phoebe/api/core/http/RetryingHttpClient.kt index d6041fe..5245d87 100644 --- a/phoebe-java-core/src/main/kotlin/com/phoebe/api/core/http/RetryingHttpClient.kt +++ b/phoebe-java-core/src/main/kotlin/com/phoebe/api/core/http/RetryingHttpClient.kt @@ -31,10 +31,6 @@ private constructor( ) : HttpClient { override fun execute(request: HttpRequest, requestOptions: RequestOptions): HttpResponse { - if (!isRetryable(request) || maxRetries <= 0) { - return httpClient.execute(request, requestOptions) - } - var modifiedRequest = maybeAddIdempotencyHeader(request) // Don't send the current retry count in the headers if the caller set their own value. @@ -48,6 +44,10 @@ private constructor( modifiedRequest = setRetryCountHeader(modifiedRequest, retries) } + if (!isRetryable(modifiedRequest)) { + return httpClient.execute(modifiedRequest, requestOptions) + } + val response = try { val response = httpClient.execute(modifiedRequest, requestOptions) @@ -75,10 +75,6 @@ private constructor( request: HttpRequest, requestOptions: RequestOptions, ): CompletableFuture { - if (!isRetryable(request) || maxRetries <= 0) { - return httpClient.executeAsync(request, requestOptions) - } - val modifiedRequest = maybeAddIdempotencyHeader(request) // Don't send the current retry count in the headers if the caller set their own value. @@ -94,8 +90,12 @@ private constructor( val requestWithRetryCount = if (shouldSendRetryCount) setRetryCountHeader(request, retries) else request - return httpClient - .executeAsync(requestWithRetryCount, requestOptions) + val responseFuture = httpClient.executeAsync(requestWithRetryCount, requestOptions) + if (!isRetryable(requestWithRetryCount)) { + return responseFuture + } + + return responseFuture .handleAsync( fun( response: HttpResponse?, From 75d7ae3dc40b246a7fe1e4a0316122c579779edd Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 17 Jan 2026 07:59:02 +0000 Subject: [PATCH 16/25] chore(ci): upgrade `actions/setup-java` --- .github/workflows/ci.yml | 6 +++--- .github/workflows/publish-sonatype.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ffa2c80..22135c2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,7 +23,7 @@ jobs: - uses: actions/checkout@v6 - name: Set up Java - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: temurin java-version: | @@ -50,7 +50,7 @@ jobs: - uses: actions/checkout@v6 - name: Set up Java - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: temurin java-version: | @@ -88,7 +88,7 @@ jobs: - uses: actions/checkout@v6 - name: Set up Java - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: temurin java-version: | diff --git a/.github/workflows/publish-sonatype.yml b/.github/workflows/publish-sonatype.yml index 1730207..3a4ee30 100644 --- a/.github/workflows/publish-sonatype.yml +++ b/.github/workflows/publish-sonatype.yml @@ -17,7 +17,7 @@ jobs: - uses: actions/checkout@v6 - name: Set up Java - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: temurin java-version: | From cf7eb239f388de322581a476a6e6845cebd5e019 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 22 Jan 2026 04:56:05 +0000 Subject: [PATCH 17/25] chore(internal): update maven repo doc to include authentication --- scripts/upload-artifacts | 64 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 59 insertions(+), 5 deletions(-) diff --git a/scripts/upload-artifacts b/scripts/upload-artifacts index 548d152..10f3c70 100755 --- a/scripts/upload-artifacts +++ b/scripts/upload-artifacts @@ -96,8 +96,52 @@ generate_instructions() {

Stainless SDK Maven Repository

This is the Maven repository for your Stainless Java SDK build.

-

Directions

-

To use the uploaded Maven repository, add the following to your project's pom.xml:

+

Project configuration

+ +

The details depend on whether you're using Maven or Gradle as your build tool.

+ +

Maven

+ +

Add the following to your project's pom.xml:

+
<repositories>
+    <repository>
+        <id>stainless-sdk-repo</id>
+        <url>https://pkg.stainless.com/s/${PROJECT}/${SHA}/mvn</url>
+    </repository>
+</repositories>
+ +

Gradle

+

Add the following to your build.gradle file:

+
repositories {
+    maven {
+        url "https://pkg.stainless.com/s/${PROJECT}/${SHA}/mvn"
+    }
+}
+ +
+

Configuring authentication (if required)

+ +

Some accounts may require authentication to access the repository. If so, use the + following instructions, replacing YOUR_STAINLESS_API_TOKEN with your actual token.

+ +

Maven with authentication

+ +

First, ensure you have the following in your Maven settings.xml for repo authentication:

+
<servers>
+    <server>
+        <id>stainless-sdk-repo</id>
+        <configuration>
+            <httpHeaders>
+                <property>
+                    <name>Authorization</name>
+                    <value>Bearer YOUR_STAINLESS_API_TOKEN</value>
+                </property>
+            </httpHeaders>
+        </configuration>
+    </server>
+</servers>
+ +

Then, add the following to your project's pom.xml:

<repositories>
     <repository>
         <id>stainless-sdk-repo</id>
@@ -105,14 +149,24 @@ generate_instructions() {
     </repository>
 </repositories>
-

If you're using Gradle, add the following to your build.gradle file:

+

Gradle with authentication

+

Add the following to your build.gradle file:

repositories {
     maven {
-        url 'https://pkg.stainless.com/s/${PROJECT}/${SHA}/mvn'
+        url "https://pkg.stainless.com/s/${PROJECT}/${SHA}/mvn"
+        credentials(HttpHeaderCredentials) {
+            name = "Authorization"
+            value = "Bearer YOUR_STAINLESS_API_TOKEN"
+        }
+        authentication {
+            header(HttpHeaderAuthentication)
+        }
     }
 }
+
-

Once you've added the repository, you can include dependencies from it as usual. See your +

Using the repository

+

Once you've configured the repository, you can include dependencies from it as usual. See your project README for more details.

From d9f18bd66b86423c4ae710ea0987fe35f53ed457 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 22 Jan 2026 04:57:04 +0000 Subject: [PATCH 18/25] feat(client): send `X-Stainless-Kotlin-Version` header --- .../src/main/kotlin/com/phoebe/api/core/ClientOptions.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/phoebe-java-core/src/main/kotlin/com/phoebe/api/core/ClientOptions.kt b/phoebe-java-core/src/main/kotlin/com/phoebe/api/core/ClientOptions.kt index cadc2c5..9efbc61 100644 --- a/phoebe-java-core/src/main/kotlin/com/phoebe/api/core/ClientOptions.kt +++ b/phoebe-java-core/src/main/kotlin/com/phoebe/api/core/ClientOptions.kt @@ -402,6 +402,7 @@ private constructor( headers.put("X-Stainless-Package-Version", getPackageVersion()) headers.put("X-Stainless-Runtime", "JRE") headers.put("X-Stainless-Runtime-Version", getJavaVersion()) + headers.put("X-Stainless-Kotlin-Version", KotlinVersion.CURRENT.toString()) apiKey.let { if (!it.isEmpty()) { headers.put("X-eBirdApiToken", it) From adfd7da05a09ef5622316700c031b6ac6ef4d05f Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 23 Jan 2026 06:59:59 +0000 Subject: [PATCH 19/25] chore(internal): correct cache invalidation for `SKIP_MOCK_TESTS` --- buildSrc/src/main/kotlin/phoebe.kotlin.gradle.kts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/buildSrc/src/main/kotlin/phoebe.kotlin.gradle.kts b/buildSrc/src/main/kotlin/phoebe.kotlin.gradle.kts index 40e3ce8..9478991 100644 --- a/buildSrc/src/main/kotlin/phoebe.kotlin.gradle.kts +++ b/buildSrc/src/main/kotlin/phoebe.kotlin.gradle.kts @@ -33,6 +33,9 @@ kotlin { tasks.withType().configureEach { systemProperty("junit.jupiter.execution.parallel.enabled", true) systemProperty("junit.jupiter.execution.parallel.mode.default", "concurrent") + + // `SKIP_MOCK_TESTS` affects which tests run so it must be added as input for proper cache invalidation. + inputs.property("skipMockTests", System.getenv("SKIP_MOCK_TESTS")).optional(true) } val ktfmt by configurations.creating From 50bb540673b947b6f5b2da24bb3cac46e3ca498a Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 23 Jan 2026 07:03:25 +0000 Subject: [PATCH 20/25] fix(client): preserve time zone in lenient date-time parsing --- .../com/phoebe/api/core/ObjectMappers.kt | 19 +++++---- .../com/phoebe/api/core/ObjectMappersTest.kt | 41 +++++++++++++++---- 2 files changed, 44 insertions(+), 16 deletions(-) diff --git a/phoebe-java-core/src/main/kotlin/com/phoebe/api/core/ObjectMappers.kt b/phoebe-java-core/src/main/kotlin/com/phoebe/api/core/ObjectMappers.kt index d69243e..db714d1 100644 --- a/phoebe-java-core/src/main/kotlin/com/phoebe/api/core/ObjectMappers.kt +++ b/phoebe-java-core/src/main/kotlin/com/phoebe/api/core/ObjectMappers.kt @@ -25,7 +25,7 @@ import java.time.DateTimeException import java.time.LocalDate import java.time.LocalDateTime import java.time.OffsetDateTime -import java.time.ZonedDateTime +import java.time.ZoneId import java.time.format.DateTimeFormatter import java.time.temporal.ChronoField @@ -157,14 +157,15 @@ private class LenientOffsetDateTimeDeserializer : val temporal = formatter.parse(p.text) return when { - !temporal.isSupported(ChronoField.HOUR_OF_DAY) -> - LocalDate.from(temporal).atStartOfDay() - !temporal.isSupported(ChronoField.OFFSET_SECONDS) -> - LocalDateTime.from(temporal) - else -> ZonedDateTime.from(temporal).toLocalDateTime() - } - .atZone(context.timeZone.toZoneId()) - .toOffsetDateTime() + !temporal.isSupported(ChronoField.HOUR_OF_DAY) -> + LocalDate.from(temporal) + .atStartOfDay() + .atZone(ZoneId.of("UTC")) + .toOffsetDateTime() + !temporal.isSupported(ChronoField.OFFSET_SECONDS) -> + LocalDateTime.from(temporal).atZone(ZoneId.of("UTC")).toOffsetDateTime() + else -> OffsetDateTime.from(temporal) + } } catch (e: DateTimeException) { exceptions.add(e) } diff --git a/phoebe-java-core/src/test/kotlin/com/phoebe/api/core/ObjectMappersTest.kt b/phoebe-java-core/src/test/kotlin/com/phoebe/api/core/ObjectMappersTest.kt index ae0747a..115b997 100644 --- a/phoebe-java-core/src/test/kotlin/com/phoebe/api/core/ObjectMappersTest.kt +++ b/phoebe-java-core/src/test/kotlin/com/phoebe/api/core/ObjectMappersTest.kt @@ -3,12 +3,14 @@ package com.phoebe.api.core import com.fasterxml.jackson.annotation.JsonProperty import com.fasterxml.jackson.databind.exc.MismatchedInputException import com.fasterxml.jackson.module.kotlin.readValue +import java.time.LocalDate +import java.time.LocalTime import java.time.OffsetDateTime +import java.time.ZoneOffset import kotlin.reflect.KClass import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.catchThrowable import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertDoesNotThrow import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.EnumSource import org.junitpioneer.jupiter.cartesian.CartesianTest @@ -72,11 +74,34 @@ internal class ObjectMappersTest { } } - enum class LenientOffsetDateTimeTestCase(val string: String) { - DATE("1998-04-21"), - DATE_TIME("1998-04-21T04:00:00"), - ZONED_DATE_TIME_1("1998-04-21T04:00:00+03:00"), - ZONED_DATE_TIME_2("1998-04-21T04:00:00Z"), + enum class LenientOffsetDateTimeTestCase( + val string: String, + val expectedOffsetDateTime: OffsetDateTime, + ) { + DATE( + "1998-04-21", + expectedOffsetDateTime = + OffsetDateTime.of(LocalDate.of(1998, 4, 21), LocalTime.of(0, 0), ZoneOffset.UTC), + ), + DATE_TIME( + "1998-04-21T04:00:00", + expectedOffsetDateTime = + OffsetDateTime.of(LocalDate.of(1998, 4, 21), LocalTime.of(4, 0), ZoneOffset.UTC), + ), + ZONED_DATE_TIME_1( + "1998-04-21T04:00:00+03:00", + expectedOffsetDateTime = + OffsetDateTime.of( + LocalDate.of(1998, 4, 21), + LocalTime.of(4, 0), + ZoneOffset.ofHours(3), + ), + ), + ZONED_DATE_TIME_2( + "1998-04-21T04:00:00Z", + expectedOffsetDateTime = + OffsetDateTime.of(LocalDate.of(1998, 4, 21), LocalTime.of(4, 0), ZoneOffset.UTC), + ), } @ParameterizedTest @@ -85,6 +110,8 @@ internal class ObjectMappersTest { val jsonMapper = jsonMapper() val json = jsonMapper.writeValueAsString(testCase.string) - assertDoesNotThrow { jsonMapper().readValue(json) } + val offsetDateTime = jsonMapper().readValue(json) + + assertThat(offsetDateTime).isEqualTo(testCase.expectedOffsetDateTime) } } From 43107c0efbf857c052a1ce799f14a45add565375 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 24 Jan 2026 06:44:49 +0000 Subject: [PATCH 21/25] chore(ci): upgrade `actions/github-script` --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 22135c2..2208be4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -67,7 +67,7 @@ jobs: - name: Get GitHub OIDC Token if: github.repository == 'stainless-sdks/phoebe-java' id: github-oidc - uses: actions/github-script@v6 + uses: actions/github-script@v8 with: script: core.setOutput('github_token', await core.getIDToken()); From 704a4e190b022602ad545cf926ef21ce4bac5d96 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 29 Jan 2026 07:32:57 +0000 Subject: [PATCH 22/25] chore(internal): codegen related update --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5027fa2..1144a6f 100644 --- a/README.md +++ b/README.md @@ -15,8 +15,8 @@ It is generated with [Stainless](https://www.stainless.com/). Use the Phoebe MCP Server to enable AI assistants to interact with this API, allowing them to explore endpoints, make test requests, and use documentation to help integrate this SDK into your application. -[![Add to Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en-US/install-mcp?name=phoebe-ebird-mcp&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsInBob2ViZS1lYmlyZC1tY3AiXX0) -[![Install in VS Code](https://img.shields.io/badge/_-Add_to_VS_Code-blue?style=for-the-badge&logo=data:image/svg%2bxml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9Im5vbmUiIHZpZXdCb3g9IjAgMCA0MCA0MCI+PHBhdGggZmlsbD0iI0VFRSIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMzAuMjM1IDM5Ljg4NGEyLjQ5MSAyLjQ5MSAwIDAgMS0xLjc4MS0uNzNMMTIuNyAyNC43OGwtMy40NiAyLjYyNC0zLjQwNiAyLjU4MmExLjY2NSAxLjY2NSAwIDAgMS0xLjA4Mi4zMzggMS42NjQgMS42NjQgMCAwIDEtMS4wNDYtLjQzMWwtMi4yLTJhMS42NjYgMS42NjYgMCAwIDEgMC0yLjQ2M0w3LjQ1OCAyMCA0LjY3IDE3LjQ1MyAxLjUwNyAxNC41N2ExLjY2NSAxLjY2NSAwIDAgMSAwLTIuNDYzbDIuMi0yYTEuNjY1IDEuNjY1IDAgMCAxIDIuMTMtLjA5N2w2Ljg2MyA1LjIwOUwyOC40NTIuODQ0YTIuNDg4IDIuNDg4IDAgMCAxIDEuODQxLS43MjljLjM1MS4wMDkuNjk5LjA5MSAxLjAxOS4yNDVsOC4yMzYgMy45NjFhMi41IDIuNSAwIDAgMSAxLjQxNSAyLjI1M3YuMDk5LS4wNDVWMzMuMzd2LS4wNDUuMDk1YTIuNTAxIDIuNTAxIDAgMCAxLTEuNDE2IDIuMjU3bC04LjIzNSAzLjk2MWEyLjQ5MiAyLjQ5MiAwIDAgMS0xLjA3Ny4yNDZabS43MTYtMjguOTQ3LTExLjk0OCA5LjA2MiAxMS45NTIgOS4wNjUtLjAwNC0xOC4xMjdaIi8+PC9zdmc+)](https://vscode.stainless.com/mcp/%7B%22name%22%3A%22phoebe-ebird-mcp%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22phoebe-ebird-mcp%22%5D%7D) +[![Add to Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en-US/install-mcp?name=phoebe-ebird-mcp&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsInBob2ViZS1lYmlyZC1tY3AiXSwiZW52Ijp7IkVCSVJEX0FQSV9LRVkiOiJNeSBBUEkgS2V5In19) +[![Install in VS Code](https://img.shields.io/badge/_-Add_to_VS_Code-blue?style=for-the-badge&logo=data:image/svg%2bxml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9Im5vbmUiIHZpZXdCb3g9IjAgMCA0MCA0MCI+PHBhdGggZmlsbD0iI0VFRSIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMzAuMjM1IDM5Ljg4NGEyLjQ5MSAyLjQ5MSAwIDAgMS0xLjc4MS0uNzNMMTIuNyAyNC43OGwtMy40NiAyLjYyNC0zLjQwNiAyLjU4MmExLjY2NSAxLjY2NSAwIDAgMS0xLjA4Mi4zMzggMS42NjQgMS42NjQgMCAwIDEtMS4wNDYtLjQzMWwtMi4yLTJhMS42NjYgMS42NjYgMCAwIDEgMC0yLjQ2M0w3LjQ1OCAyMCA0LjY3IDE3LjQ1MyAxLjUwNyAxNC41N2ExLjY2NSAxLjY2NSAwIDAgMSAwLTIuNDYzbDIuMi0yYTEuNjY1IDEuNjY1IDAgMCAxIDIuMTMtLjA5N2w2Ljg2MyA1LjIwOUwyOC40NTIuODQ0YTIuNDg4IDIuNDg4IDAgMCAxIDEuODQxLS43MjljLjM1MS4wMDkuNjk5LjA5MSAxLjAxOS4yNDVsOC4yMzYgMy45NjFhMi41IDIuNSAwIDAgMSAxLjQxNSAyLjI1M3YuMDk5LS4wNDVWMzMuMzd2LS4wNDUuMDk1YTIuNTAxIDIuNTAxIDAgMCAxLTEuNDE2IDIuMjU3bC04LjIzNSAzLjk2MWEyLjQ5MiAyLjQ5MiAwIDAgMS0xLjA3Ny4yNDZabS43MTYtMjguOTQ3LTExLjk0OCA5LjA2MiAxMS45NTIgOS4wNjUtLjAwNC0xOC4xMjdaIi8+PC9zdmc+)](https://vscode.stainless.com/mcp/%7B%22name%22%3A%22phoebe-ebird-mcp%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22phoebe-ebird-mcp%22%5D%2C%22env%22%3A%7B%22EBIRD_API_KEY%22%3A%22My%20API%20Key%22%7D%7D) > Note: You may need to set environment variables in your MCP client. From 78f64241e6e1a7f11b3004ea3b40e282688e473d Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 31 Jan 2026 08:31:27 +0000 Subject: [PATCH 23/25] chore(internal): codegen related update --- scripts/build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/build b/scripts/build index f406348..16a2b00 100755 --- a/scripts/build +++ b/scripts/build @@ -5,4 +5,4 @@ set -e cd "$(dirname "$0")/.." echo "==> Building classes" -./gradlew build testClasses -x test +./gradlew build testClasses "$@" -x test From fa744740ab0421e1a3ed33aad5952fde2e80c4df Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 7 Feb 2026 10:19:36 +0000 Subject: [PATCH 24/25] chore(internal): codegen related update --- phoebe-java-client-okhttp/build.gradle.kts | 2 +- phoebe-java-core/build.gradle.kts | 2 +- phoebe-java-proguard-test/build.gradle.kts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/phoebe-java-client-okhttp/build.gradle.kts b/phoebe-java-client-okhttp/build.gradle.kts index d69a5de..6ae5611 100644 --- a/phoebe-java-client-okhttp/build.gradle.kts +++ b/phoebe-java-client-okhttp/build.gradle.kts @@ -10,6 +10,6 @@ dependencies { implementation("com.squareup.okhttp3:logging-interceptor:4.12.0") testImplementation(kotlin("test")) - testImplementation("org.assertj:assertj-core:3.25.3") + testImplementation("org.assertj:assertj-core:3.27.7") testImplementation("com.github.tomakehurst:wiremock-jre8:2.35.2") } diff --git a/phoebe-java-core/build.gradle.kts b/phoebe-java-core/build.gradle.kts index bf0d85a..c5441df 100644 --- a/phoebe-java-core/build.gradle.kts +++ b/phoebe-java-core/build.gradle.kts @@ -33,7 +33,7 @@ dependencies { testImplementation(kotlin("test")) testImplementation(project(":phoebe-java-client-okhttp")) testImplementation("com.github.tomakehurst:wiremock-jre8:2.35.2") - testImplementation("org.assertj:assertj-core:3.25.3") + testImplementation("org.assertj:assertj-core:3.27.7") testImplementation("org.junit.jupiter:junit-jupiter-api:5.9.3") testImplementation("org.junit.jupiter:junit-jupiter-params:5.9.3") testImplementation("org.junit-pioneer:junit-pioneer:1.9.1") diff --git a/phoebe-java-proguard-test/build.gradle.kts b/phoebe-java-proguard-test/build.gradle.kts index 7bcadd4..248bccd 100644 --- a/phoebe-java-proguard-test/build.gradle.kts +++ b/phoebe-java-proguard-test/build.gradle.kts @@ -18,7 +18,7 @@ dependencies { testImplementation(project(":phoebe-java")) testImplementation(kotlin("test")) testImplementation("org.junit.jupiter:junit-jupiter-api:5.9.3") - testImplementation("org.assertj:assertj-core:3.25.3") + testImplementation("org.assertj:assertj-core:3.27.7") testImplementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.14.0") } From f5e4fe7b710992c0b9c4d456e01cb2e66c73fbc7 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 7 Feb 2026 10:20:01 +0000 Subject: [PATCH 25/25] release: 0.1.0-alpha.1 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 83 +++++++++++++++++++++++++++++++++++ README.md | 10 ++--- build.gradle.kts | 2 +- 4 files changed, 90 insertions(+), 7 deletions(-) create mode 100644 CHANGELOG.md diff --git a/.release-please-manifest.json b/.release-please-manifest.json index c476280..ba6c348 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.0.1-alpha.0" + ".": "0.1.0-alpha.1" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..c8b184d --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,83 @@ +# Changelog + +## 0.1.0-alpha.1 (2026-02-07) + +Full Changelog: [v0.0.1-alpha.0...v0.1.0-alpha.1](https://github.com/phoebe-bird/phoebe-java/compare/v0.0.1-alpha.0...v0.1.0-alpha.1) + +### ⚠ BREAKING CHANGES + +* **client:** change precision of some numeric types +* **client:** extract auto pagination to shared classes +* **client:** **Migration:** - If you were referencing the `AutoPager` class on a specific `*Page` or `*PageAsync` type, then you should instead reference the shared `AutoPager` and `AutoPagerAsync` types, under the `core` package + - `AutoPagerAsync` now has different usage. You can call `.subscribe(...)` on the returned object instead to get called back each page item. You can also call `onCompleteFuture()` to get a future that completes when all items have been processed. Finally, you can call `.close()` on the returned object to stop auto-paginating early + - If you were referencing `getNextPage` or `getNextPageParams`: + - Swap to `nextPage()` and `nextPageParams()` + - Note that these both now return non-optional types (use `hasNextPage()` before calling these, since they will throw if it's impossible to get another page) + +### Features + +* **api:** manual updates ([d712abb](https://github.com/phoebe-bird/phoebe-java/commit/d712abbfc3f4e8e54e8391ae34616bae45510b27)) +* **client:** add `{QueryParams,Headers}#put(String, JsonValue)` methods ([31f7acd](https://github.com/phoebe-bird/phoebe-java/commit/31f7acd90ff3c222f4db984e29ef8a090ce557d6)) +* **client:** add `HttpRequest#url()` method ([a145b94](https://github.com/phoebe-bird/phoebe-java/commit/a145b94467e13633c59889cccd0c26b0b3789a44)) +* **client:** add a `withOptions` method ([008f2ee](https://github.com/phoebe-bird/phoebe-java/commit/008f2ee04e6216121f6bfdf31616a69f0143d55e)) +* **client:** add https config options ([74d088e](https://github.com/phoebe-bird/phoebe-java/commit/74d088e2486ecc1d43474dc0e928905c7eecdfe8)) +* **client:** allow configuring dispatcher executor service ([ef7c266](https://github.com/phoebe-bird/phoebe-java/commit/ef7c2660841ba59c269caa78b58f9a9f59fa162c)) +* **client:** allow configuring env via system properties ([f1de279](https://github.com/phoebe-bird/phoebe-java/commit/f1de279091ccdbf3ace91ed860f1d00345fb6c71)) +* **client:** allow providing some params positionally ([b24d6c0](https://github.com/phoebe-bird/phoebe-java/commit/b24d6c0cd3ecc5a55a382454f96bbf89a84fd3d7)) +* **client:** extract auto pagination to shared classes ([13f571f](https://github.com/phoebe-bird/phoebe-java/commit/13f571f7baf0caef24ce9c554b244476ed3e812b)) +* **client:** implement per-endpoint base URL support ([f493836](https://github.com/phoebe-bird/phoebe-java/commit/f4938363ced1c863b72b10c787c73c56ab6aac51)) +* **client:** send `X-Stainless-Kotlin-Version` header ([d9f18bd](https://github.com/phoebe-bird/phoebe-java/commit/d9f18bd66b86423c4ae710ea0987fe35f53ed457)) + + +### Bug Fixes + +* **client:** bump max requests per host to max requests (5 -> 64) ([73b4311](https://github.com/phoebe-bird/phoebe-java/commit/73b4311a77e11406a4c591ef9bf111ef84970605)) +* **client:** cancel okhttp call when future cancelled ([5823aba](https://github.com/phoebe-bird/phoebe-java/commit/5823abafce2a92fbb69a0f6c23db79a78b78f2a8)) +* **client:** disallow coercion from float to int ([d00944f](https://github.com/phoebe-bird/phoebe-java/commit/d00944fd323009658808104c8a4b3954211b3559)) +* **client:** don't close client on `withOptions` usage when original is gc'd ([3d3f673](https://github.com/phoebe-bird/phoebe-java/commit/3d3f673ecda4ce417d1ec954ea670cf629ae373a)) +* **client:** ensure error handling always occurs ([c4327a3](https://github.com/phoebe-bird/phoebe-java/commit/c4327a3c088df1b9398337114f3316f5e355a7f3)) +* **client:** fully respect max retries ([7c71f9d](https://github.com/phoebe-bird/phoebe-java/commit/7c71f9dc52f85d1a055e25f5d940ac1fc6d807b0)) +* **client:** preserve time zone in lenient date-time parsing ([50bb540](https://github.com/phoebe-bird/phoebe-java/commit/50bb540673b947b6f5b2da24bb3cac46e3ca498a)) +* **client:** remove `@MustBeClosed` for future returning methods ([a6e5c78](https://github.com/phoebe-bird/phoebe-java/commit/a6e5c78fcb9b3107791323d3c3da12791634939a)) +* **client:** send retry count header for max retries 0 ([7c71f9d](https://github.com/phoebe-bird/phoebe-java/commit/7c71f9dc52f85d1a055e25f5d940ac1fc6d807b0)) +* date time deserialization leniency ([9d5f042](https://github.com/phoebe-bird/phoebe-java/commit/9d5f042b85f9bdaec3a56ad6ea180a32f6a5f454)) + + +### Chores + +* **ci:** enable for pull requests ([30e9e4a](https://github.com/phoebe-bird/phoebe-java/commit/30e9e4af5c35b09de9c58934e263d3a6b667d3fa)) +* **ci:** only run for pushes and fork pull requests ([a3e1190](https://github.com/phoebe-bird/phoebe-java/commit/a3e119093c0f9c11ee28bef632aca42deb6635b2)) +* **ci:** upgrade `actions/github-script` ([43107c0](https://github.com/phoebe-bird/phoebe-java/commit/43107c0efbf857c052a1ce799f14a45add565375)) +* **ci:** upgrade `actions/setup-java` ([75d7ae3](https://github.com/phoebe-bird/phoebe-java/commit/75d7ae3dc40b246a7fe1e4a0316122c579779edd)) +* configure new SDK language ([30ab2b2](https://github.com/phoebe-bird/phoebe-java/commit/30ab2b26361936249dbea4db64787cfc7c6b7e2d)) +* **docs:** grammar improvements ([33fa7c5](https://github.com/phoebe-bird/phoebe-java/commit/33fa7c53610ea31ad671e5c5437fa6d4d02290a9)) +* **internal:** allow running specific example from cli ([56695c9](https://github.com/phoebe-bird/phoebe-java/commit/56695c9e55d80e78608e41f4f90297f13d100c22)) +* **internal:** clean up maven repo artifact script and add html documentation to repo root ([6a0f2ca](https://github.com/phoebe-bird/phoebe-java/commit/6a0f2cab131804296e2b29cc2e29c58de3603a2b)) +* **internal:** codegen related update ([fa74474](https://github.com/phoebe-bird/phoebe-java/commit/fa744740ab0421e1a3ed33aad5952fde2e80c4df)) +* **internal:** codegen related update ([78f6424](https://github.com/phoebe-bird/phoebe-java/commit/78f64241e6e1a7f11b3004ea3b40e282688e473d)) +* **internal:** codegen related update ([704a4e1](https://github.com/phoebe-bird/phoebe-java/commit/704a4e190b022602ad545cf926ef21ce4bac5d96)) +* **internal:** codegen related update ([e57fa55](https://github.com/phoebe-bird/phoebe-java/commit/e57fa556588b355f889962a6e57cf041a2ab7b5c)) +* **internal:** codegen related update ([44125db](https://github.com/phoebe-bird/phoebe-java/commit/44125db5522d48fb720c0753aeada71cab6e3105)) +* **internal:** correct cache invalidation for `SKIP_MOCK_TESTS` ([adfd7da](https://github.com/phoebe-bird/phoebe-java/commit/adfd7da05a09ef5622316700c031b6ac6ef4d05f)) +* **internal:** depend on packages directly in example ([7c71f9d](https://github.com/phoebe-bird/phoebe-java/commit/7c71f9dc52f85d1a055e25f5d940ac1fc6d807b0)) +* **internal:** improve maven repo docs ([da49cb7](https://github.com/phoebe-bird/phoebe-java/commit/da49cb77739c9becf5d45b12095298950f1e32f9)) +* **internal:** refactor delegating from client to options ([4ea1ff5](https://github.com/phoebe-bird/phoebe-java/commit/4ea1ff5ee41e8e81d62000b0bac199c47d645b8e)) +* **internal:** remove unnecessary `[...]` in `[@see](https://github.com/see)` ([bed463e](https://github.com/phoebe-bird/phoebe-java/commit/bed463e241505d386c28f49a7c0edbfaa9c4e3be)) +* **internal:** support uploading Maven repo artifacts to stainless package server ([37c89f8](https://github.com/phoebe-bird/phoebe-java/commit/37c89f8c816525461bce82b5550d796ff1ff3023)) +* **internal:** update `actions/checkout` version ([3a146bb](https://github.com/phoebe-bird/phoebe-java/commit/3a146bbdb3ab84f558391f3b8e6bc7e2c8fd77f6)) +* **internal:** update maven repo doc to include authentication ([cf7eb23](https://github.com/phoebe-bird/phoebe-java/commit/cf7eb239f388de322581a476a6e6845cebd5e019)) +* test on Jackson 2.14.0 to avoid encountering FasterXML/jackson-databind[#3240](https://github.com/phoebe-bird/phoebe-java/issues/3240) in tests ([9d5f042](https://github.com/phoebe-bird/phoebe-java/commit/9d5f042b85f9bdaec3a56ad6ea180a32f6a5f454)) + + +### Documentation + +* fix missing readme comment ([4fd91a7](https://github.com/phoebe-bird/phoebe-java/commit/4fd91a77d9e1291a5d81ee0cc950d4a83e2ab753)) +* more code comments ([f8420f4](https://github.com/phoebe-bird/phoebe-java/commit/f8420f4dfd2e03fae3e61c5b7884f425c92b69d8)) +* prominently feature MCP server setup in root SDK readmes ([7754ce1](https://github.com/phoebe-bird/phoebe-java/commit/7754ce1495022f309ff26d5c2f06f3c4c8e913b7)) +* remove `$` for better copy-pasteabality ([fe8a020](https://github.com/phoebe-bird/phoebe-java/commit/fe8a02007be585c2c162442e968a4fe8909eb39e)) + + +### Refactors + +* **client:** change precision of some numeric types ([c593d52](https://github.com/phoebe-bird/phoebe-java/commit/c593d52bf58fc1324aef2f61d1411a0923b15a2e)) +* **internal:** minor `ClientOptionsTest` change ([b9a8ca8](https://github.com/phoebe-bird/phoebe-java/commit/b9a8ca82aa21e634734848084170f7930b906684)) diff --git a/README.md b/README.md index 1144a6f..13b5e6c 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,8 @@ -[![Maven Central](https://img.shields.io/maven-central/v/com.phoebe.api/phoebe-java)](https://central.sonatype.com/artifact/com.phoebe.api/phoebe-java/0.0.1-alpha.0) -[![javadoc](https://javadoc.io/badge2/com.phoebe.api/phoebe-java/0.0.1-alpha.0/javadoc.svg)](https://javadoc.io/doc/com.phoebe.api/phoebe-java/0.0.1-alpha.0) +[![Maven Central](https://img.shields.io/maven-central/v/com.phoebe.api/phoebe-java)](https://central.sonatype.com/artifact/com.phoebe.api/phoebe-java/0.1.0-alpha.1) +[![javadoc](https://javadoc.io/badge2/com.phoebe.api/phoebe-java/0.1.0-alpha.1/javadoc.svg)](https://javadoc.io/doc/com.phoebe.api/phoebe-java/0.1.0-alpha.1) @@ -22,7 +22,7 @@ Use the Phoebe MCP Server to enable AI assistants to interact with this API, all -The REST API documentation can be found on [science.ebird.org](https://science.ebird.org/en/use-ebird-data/download-ebird-data-products). Javadocs are available on [javadoc.io](https://javadoc.io/doc/com.phoebe.api/phoebe-java/0.0.1-alpha.0). +The REST API documentation can be found on [science.ebird.org](https://science.ebird.org/en/use-ebird-data/download-ebird-data-products). Javadocs are available on [javadoc.io](https://javadoc.io/doc/com.phoebe.api/phoebe-java/0.1.0-alpha.1). @@ -33,7 +33,7 @@ The REST API documentation can be found on [science.ebird.org](https://science.e ### Gradle ```kotlin -implementation("com.phoebe.api:phoebe-java:0.0.1-alpha.0") +implementation("com.phoebe.api:phoebe-java:0.1.0-alpha.1") ``` ### Maven @@ -42,7 +42,7 @@ implementation("com.phoebe.api:phoebe-java:0.0.1-alpha.0") com.phoebe.api phoebe-java - 0.0.1-alpha.0 + 0.1.0-alpha.1 ``` diff --git a/build.gradle.kts b/build.gradle.kts index bccbd2e..dd5d657 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -9,7 +9,7 @@ repositories { allprojects { group = "com.phoebe.api" - version = "0.0.1-alpha.0" // x-release-please-version + version = "0.1.0-alpha.1" // x-release-please-version } subprojects {