(json)
+
+ assertThat(offsetDateTime).isEqualTo(testCase.expectedOffsetDateTime)
}
}
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)
+ }
+}
diff --git a/phoebe-java-proguard-test/build.gradle.kts b/phoebe-java-proguard-test/build.gradle.kts
index 932d96a..248bccd 100644
--- a/phoebe-java-proguard-test/build.gradle.kts
+++ b/phoebe-java-proguard-test/build.gradle.kts
@@ -18,8 +18,8 @@ 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("com.fasterxml.jackson.module:jackson-module-kotlin:2.13.4")
+ testImplementation("org.assertj:assertj-core:3.27.7")
+ testImplementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.14.0")
}
tasks.shadowJar {
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
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
diff --git a/scripts/upload-artifacts b/scripts/upload-artifacts
new file mode 100755
index 0000000..10f3c70
--- /dev/null
+++ b/scripts/upload-artifacts
@@ -0,0 +1,193 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+# ANSI Color Codes
+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"
+ 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#"${MAVEN_REPO_PATH}"}"
+
+ # 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" ;;
+ html) content_type="text/html" ;;
+ *) content_type="application/octet-stream" ;;
+ esac
+
+ # 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 to 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
+}
+
+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.
+
+ 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>
+ <url>https://pkg.stainless.com/s/${PROJECT}/${SHA}/mvn</url>
+ </repository>
+</repositories>
+
+ Gradle with authentication
+ Add the following to your build.gradle file:
+ repositories {
+ maven {
+ url "https://pkg.stainless.com/s/${PROJECT}/${SHA}/mvn"
+ credentials(HttpHeaderCredentials) {
+ name = "Authorization"
+ value = "Bearer YOUR_STAINLESS_API_TOKEN"
+ }
+ authentication {
+ header(HttpHeaderAuthentication)
+ }
+ }
+}
+
+
+ Using the repository
+ Once you've configured the repository, you can include dependencies from it as usual. See your
+ project README
+ for more details.
+
+
+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"
+./gradlew publishMavenPublicationToLocalFileSystemRepository -PpublishLocal
+echo "::endgroup::"
+
+echo "::group::Uploading to pkg.stainless.com"
+walk_tree "$MAVEN_REPO_PATH"
+echo "::endgroup::"
+
+echo "::group::Generating instructions"
+generate_instructions
+echo "::endgroup::"