diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 70a86f88..448e307e 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -1,6 +1,8 @@
name: build
-on: [pull_request]
+on:
+ push:
+ branches-ignore: [main]
jobs:
build:
@@ -11,8 +13,5 @@ jobs:
with: { java-version: 17, distribution: temurin }
- name: Build and Verify
run: |
- # Build the plugin first and check its contents
- ./gradlew :openapi-generator-plugin:build
-
- # Now run the full build
- ./gradlew clean build
+ # With convention plugins, everything is self-contained
+ ./gradlew -PisSnapshot=true clean build
diff --git a/.github/workflows/pr-snapshot.yml b/.github/workflows/pr-snapshot.yml
new file mode 100644
index 00000000..bb92062f
--- /dev/null
+++ b/.github/workflows/pr-snapshot.yml
@@ -0,0 +1,45 @@
+name: PR Snapshot
+
+on:
+ push:
+ branches-ignore:
+ - main
+
+jobs:
+ build-and-publish-snapshot:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-java@v4
+ with:
+ java-version: 17
+ distribution: temurin
+
+ - name: Setup Gradle
+ uses: gradle/actions/setup-gradle@v4
+
+ - name: Build and Publish Snapshot
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ GITHUB_ACTOR: ${{ github.actor }}
+ # Sonatype credentials
+ OSSRH_USERNAME: ${{ secrets.MAVEN_CENTRAL_CREDS_TOKEN_USER }}
+ OSSRH_PASSWORD: ${{ secrets.MAVEN_CENTRAL_CREDS_TOKEN_PASS }}
+ # Optional signing environment variables for consistency with publish workflow
+ SIGNING_KEY: ${{ secrets.ORG_GRADLE_PROJECT_signingKey || '' }}
+ SIGNING_PASSWORD: ${{ secrets.ORG_GRADLE_PROJECT_signingPassword || '' }}
+ run: |
+ # Build the plugin first
+ ./gradlew -PisSnapshot=true :openapi-generator-plugin:build :openapi-generator-plugin:publishToMavenLocal
+
+ # Set up parameters based on available secrets
+ GRADLE_PARAMS="-PisSnapshot=true"
+ if [ -n "$SIGNING_KEY" ] && [ -n "$SIGNING_PASSWORD" ]; then
+ GRADLE_PARAMS="$GRADLE_PARAMS -P signingKey=$SIGNING_KEY -P signingPassword=$SIGNING_PASSWORD"
+ fi
+
+ # Publish snapshots to GitHub Packages
+ ./gradlew -PisSnapshot=true \
+ -P ossrhUsername=${{ secrets.MAVEN_CENTRAL_CREDS_TOKEN_USER }} \
+ -P ossrhPassword=${{ secrets.MAVEN_CENTRAL_CREDS_TOKEN_PASS }} \
+ $GRADLE_PARAMS :api-client-library:publishToSonatype
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index 2f66e133..00ca3981 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -35,8 +35,8 @@ jobs:
./gradlew \
-P signingKey=${{ secrets.ORG_GRADLE_PROJECT_signingKey }} \
-P signingPassword=${{ secrets.ORG_GRADLE_PROJECT_signingPassword }} \
- -P ossrhUsername=${{ secrets.OSSRH_USERNAME }} \
- -P ossrhPassword=${{ secrets.OSSRH_PASSWORD }} \
+ -P ossrhUsername=${{ secrets.MAVEN_CENTRAL_CREDS_TOKEN_USER }} \
+ -P ossrhPassword=${{ secrets.MAVEN_CENTRAL_CREDS_TOKEN_PASS }} \
:api-client-library:publishToSonatype \
- :api-client-library:closeAndReleaseSonatypeStagingRepository
+ closeAndReleaseSonatypeStagingRepository
./scripts/publish.sh
diff --git a/.github/workflows/version-check.yml b/.github/workflows/version-check.yml
new file mode 100644
index 00000000..9698c6a7
--- /dev/null
+++ b/.github/workflows/version-check.yml
@@ -0,0 +1,111 @@
+name: Version Check
+
+on:
+ pull_request:
+ branches: [ main ]
+
+jobs:
+ version-check:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0 # Fetch full history to compare branches
+
+ - uses: actions/setup-java@v4
+ with:
+ java-version: 17
+ distribution: temurin
+
+ - name: Check Version Increment
+ run: |
+ # Extract current version from build.gradle
+ CURRENT_VERSION=$(grep "def projectVersion" build.gradle | sed -E "s/.*def projectVersion = ['\"]([^'\"]*)['\"].*/\1/")
+ echo "Current version: $CURRENT_VERSION"
+
+ # Validate that we extracted current version correctly
+ if [ -z "$CURRENT_VERSION" ]; then
+ echo "❌ Failed to extract current version from build.gradle"
+ exit 1
+ fi
+
+ # Get the latest Git tag from the target branch
+ git fetch origin ${{ github.base_ref }}
+ git checkout origin/${{ github.base_ref }}
+
+ LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
+
+ if [ -z "$LATEST_TAG" ]; then
+ echo "No existing tags found. Assuming this is the first release."
+ LATEST_TAG="0.0.0"
+ fi
+
+ # Remove 'v' prefix if present
+ TARGET_VERSION=$(echo "$LATEST_TAG" | sed 's/^v//')
+ echo "Latest tag version: $TARGET_VERSION"
+
+ # Switch back to PR branch
+ git checkout ${{ github.head_ref }}
+
+ # Function to normalize version (ensure 3 parts)
+ normalize_version() {
+ local version=$1
+ IFS='.' read -ra V <<< "$version"
+
+ # Ensure we have 3 parts
+ while [ ${#V[@]} -lt 3 ]; do V+=(0); done
+
+ echo "${V[0]}.${V[1]}.${V[2]}"
+ }
+
+ # Function to compare semantic versions
+ version_compare() {
+ local version1=$1
+ local version2=$2
+
+ # Normalize both versions
+ version1=$(normalize_version "$version1")
+ version2=$(normalize_version "$version2")
+
+ # Split versions into arrays
+ IFS='.' read -ra V1 <<< "$version1"
+ IFS='.' read -ra V2 <<< "$version2"
+
+ # Compare major version
+ if [ "${V1[0]}" -gt "${V2[0]}" ]; then
+ return 0 # version1 > version2
+ elif [ "${V1[0]}" -lt "${V2[0]}" ]; then
+ return 1 # version1 < version2
+ fi
+
+ # Compare minor version
+ if [ "${V1[1]}" -gt "${V2[1]}" ]; then
+ return 0
+ elif [ "${V1[1]}" -lt "${V2[1]}" ]; then
+ return 1
+ fi
+
+ # Compare patch version
+ if [ "${V1[2]}" -gt "${V2[2]}" ]; then
+ return 0
+ elif [ "${V1[2]}" -lt "${V2[2]}" ]; then
+ return 1
+ fi
+
+ # Versions are equal
+ return 2
+ }
+
+ # Compare versions
+ if version_compare "$CURRENT_VERSION" "$TARGET_VERSION"; then
+ echo "✅ Version increment detected: $TARGET_VERSION → $CURRENT_VERSION"
+ exit 0
+ elif [ $? -eq 2 ]; then
+ echo "❌ Version not incremented: $CURRENT_VERSION is the same as $TARGET_VERSION"
+ echo "Please increment the version in build.gradle"
+ exit 1
+ else
+ echo "❌ Version decreased: $TARGET_VERSION → $CURRENT_VERSION"
+ echo "Version should only increase, not decrease"
+ exit 1
+ fi
diff --git a/MULTI_MODULE_README.md b/MULTI_MODULE_README.md
index 8138fd60..b965b1a1 100644
--- a/MULTI_MODULE_README.md
+++ b/MULTI_MODULE_README.md
@@ -1,54 +1,26 @@
-# Vertex API Client Java - Multi-Module Project
+# Vertex API Client Java - Project Architecture
-This project has been restructured as a multi-module Gradle project with the following modules:
+This project uses a modern Gradle multi-module structure with convention plugins for clean, maintainable builds.
-## Modules
+## Project Structure
-### 1. `openapi-generator-plugin`
-Custom Gradle plugin for generating Vertex API client code using OpenAPI Generator.
+### Core Modules
-**Location**: `openapi-generator-plugin/`
-
-**Purpose**:
-- Contains custom OpenAPI code generation logic
-- Provides a reusable Gradle plugin for generating the API client
-- Includes any custom templates or generators specific to Vertex API
-
-**Build**:
-```bash
-./gradlew :openapi-generator-plugin:build
-./gradlew :openapi-generator-plugin:publishToMavenLocal
-```
-
-### 2. `api-client-library`
+#### `api-client-library/`
The main API client library generated from the OpenAPI specification.
-**Location**: `api-client-library/`
-
**Purpose**:
- Contains the generated API client code
-- Includes authentication utilities
-- Provides the core SDK functionality
-
-**Build**:
-```bash
-./gradlew :api-client-library:build
-```
+- Includes authentication utilities and models
+- Provides the core SDK functionality that gets published to Maven Central
-**Generate API Client**:
-```bash
-./gradlew :api-client-library:openApiGenerate
-```
-
-### 3. `examples`
+#### `examples/`
Example applications demonstrating how to use the API client library.
-**Location**: `examples/`
-
**Purpose**:
-- Contains example code showing API usage
+- Contains example code showing API usage patterns
- Includes command-line utilities for common operations
-- Demonstrates best practices for using the API client
+- Demonstrates best practices for authentication and API calls
**Run Examples**:
```bash
@@ -57,50 +29,46 @@ Example applications demonstrating how to use the API client library.
./gradlew :examples:listExamples
```
-## Build Order
-
-The modules have dependencies on each other and should be built in this order:
+### Build Infrastructure
-1. `openapi-generator-plugin` (standalone)
-2. `api-client-library` (uses the plugin)
-3. `examples` (depends on the library)
+#### `buildSrc/`
+Contains the build logic and custom OpenAPI generator.
-## Building the Entire Project
+**Purpose**:
+- Houses convention plugins (`vertex.java-conventions`, `vertex.openapi-generator`)
+- Contains the custom `VertexJavaClientCodegen` generator
+- Provides shared build configuration across all modules
+- Eliminates circular dependencies and external plugin publishing
-To build all modules:
-```bash
-./gradlew build
-```
+**Key Files**:
+- `src/main/java/com/vertexvis/codegen/VertexJavaClientCodegen.java` - Custom OpenAPI generator
+- `src/main/groovy/vertex.java-conventions.gradle` - Common Java build settings
+- `src/main/groovy/vertex.openapi-generator.gradle` - OpenAPI generation configuration
-To clean and rebuild everything:
-```bash
-./gradlew clean build
-```
+## Architecture Benefits
-## Publishing
+### Convention Plugins
+- **Self-contained**: No need to publish plugins separately
+- **Consistent**: Shared configuration across all modules
+- **Maintainable**: Build logic lives with the code it serves
-The library can be published to Maven repositories:
-```bash
-./gradlew :api-client-library:publishToMavenLocal
-./gradlew publish
-```
+### Simplified Dependencies
+- **No circular dependencies**: buildSrc → modules (not modules → plugins → modules)
+- **Faster builds**: No separate plugin build/publish cycle
+- **Easier development**: Make changes and build immediately
## Development Workflow
-1. Make changes to the OpenAPI generator plugin if needed
-2. Build and publish the plugin locally: `./gradlew :openapi-generator-plugin:publishToMavenLocal`
-3. Regenerate the API client: `./gradlew :api-client-library:openApiGenerate`
-4. Build the library: `./gradlew :api-client-library:build`
-5. Test with examples: `./gradlew :examples:run`
+The build is completely self-contained:
-## Migration Notes
+```bash
+# Everything needed is built automatically
+./gradlew build
-This project was converted from a single-module to a multi-module structure:
+# Test examples
+./gradlew :examples:run
-- **Original structure**: All code in `src/main/java/com/vertexvis/`
-- **New structure**:
- - Code generation logic → `openapi-generator-plugin/`
- - Core API client → `api-client-library/`
- - Examples → `examples/`
+# Publish locally for testing
+./gradlew :api-client-library:publishToMavenLocal
+```
-The functionality remains the same, but the code is now better organized and the generator can be reused across projects.
diff --git a/README.md b/README.md
index 45801d1d..a6812836 100644
--- a/README.md
+++ b/README.md
@@ -17,7 +17,7 @@ The client can be used with Java 1.8+ and pulled into Maven or Gradle projects.
com.vertexvis
api-client-java
- 0.10.0
+ 0.11.0
compile
```
@@ -25,13 +25,13 @@ The client can be used with Java 1.8+ and pulled into Maven or Gradle projects.
### Gradle
```groovy
-compile "com.vertexvis:api-client-java:0.10.0"
+compile "com.vertexvis:api-client-java:0.11.0"
```
### Sbt
```sbt
-libraryDependencies += "com.vertexvis" % "api-client-java" % "0.10.0"
+libraryDependencies += "com.vertexvis" % "api-client-java" % "0.11.0"
```
### Others
@@ -44,7 +44,7 @@ mvn clean package
Then manually install the following JARs.
-- `target/api-client-java-0.10.0.jar`
+- `target/api-client-java-0.11.0.jar`
- `target/lib/*.jar`
## Usage
@@ -63,37 +63,64 @@ Then, check out our [sample applications](./src/main/java/com/vertexvis/example)
## Local Development
-This project uses a multi-module Gradle structure. For detailed information about the modules and their purposes, refer to the [Multi-Module README](./MULTI_MODULE_README.md).
+This project uses a multi-module Gradle structure with convention plugins. For detailed information about the modules and their purposes, refer to the [Multi-Module README](./MULTI_MODULE_README.md).
-### Build Order
+### Quick Start
-1. Build the OpenAPI Generator Plugin:
-```bash
-./gradlew :openapi-generator-plugin:build
-./gradlew :openapi-generator-plugin:publishToMavenLocal
-```
+The project now uses convention plugins in `buildSrc/` which makes the build completely self-contained:
-2. Generate the API Client:
```bash
-./gradlew :api-client-library:openApiGenerate
-```
-
-3. Build the API Client Library:
-```bash
-./gradlew :api-client-library:build
-```
+# Build everything (no separate plugin publishing needed)
+./gradlew build
-4. Run Example Applications:
-```bash
+# Run example applications
./gradlew :examples:run
./gradlew :examples:listExamples
```
-### Building the Entire Project
+### Development Workflow
-To build all modules:
-```bash
-./gradlew build
+1. **Make changes** to the API client or custom generator
+2. **Build and test** with `./gradlew build`
+3. **Test locally** with `./gradlew :api-client-library:publishToMavenLocal`
+
+### Using Snapshot Versions
+
+To consume published snapshot versions in other projects, add the snapshot repository to your build configuration:
+
+#### Maven
+
+```xml
+
+
+ central-snapshots
+ https://central.sonatype.com/repository/maven-snapshots/
+
+ true
+
+
+
+
+
+ com.vertexvis
+ api-client-java
+ 0.11.0-SNAPSHOT
+
+```
+
+#### Gradle
+
+```groovy
+repositories {
+ mavenCentral()
+ maven {
+ url 'https://central.sonatype.com/repository/maven-snapshots/'
+ }
+}
+
+dependencies {
+ implementation 'com.vertexvis:api-client-java:0.11.0-SNAPSHOT'
+}
```
### Versioning
@@ -105,12 +132,9 @@ To bump the version of all modules:
### Publishing
-To publish to Maven Local:
+To publish to Maven Local for testing:
```bash
./gradlew :api-client-library:publishToMavenLocal
```
-To publish to Maven Central:
-```bash
-./gradlew publish
-```
+Snapshots are automatically published to Maven Central on pushes to the `main` branch. Releases are created when a new version tag is pushed.
diff --git a/api-client-library/build.gradle b/api-client-library/build.gradle
index c2bbde60..ae7d3b12 100644
--- a/api-client-library/build.gradle
+++ b/api-client-library/build.gradle
@@ -1,13 +1,6 @@
-buildscript{
- dependencies {
- classpath files("$rootDir/openapi-generator-plugin/build/libs/openapi-generator-plugin-0.11.0.jar")
- }
-}
plugins {
- id 'java-library'
- id 'maven-publish'
- id 'signing'
- id 'org.openapi.generator' version '7.14.0'
+ id 'vertex.java-conventions'
+ id 'vertex.openapi-generator'
}
description = 'Vertex API Client Library for Java'
@@ -29,108 +22,26 @@ dependencies {
testImplementation("com.squareup.okhttp3:mockwebserver:4.12.0")
}
-openApiGenerate {
- verbose = false
- generatorName = 'vertex-java' // Use our custom generator
- generateModelTests = false
- generateApiTests = false
- generateModelDocumentation = false
- remoteInputSpec = 'https://platform.vertexvis.com/spec'
- outputDir = "${buildDir}/generated/"
- invokerPackage = 'com.vertexvis'
- modelPackage = 'com.vertexvis.model'
- apiPackage = 'com.vertexvis.api'
- templateDir = "${project(':openapi-generator-plugin').projectDir}/src/main/resources/vertex-java"
- configOptions = [
- openApiNullable: "false",
- dateLibrary: "java8",
- hideGenerationTimestamp: "true",
- useRuntimeException: "true",
- ]
- additionalProperties = [
- skipValidationFor: "Part,PartData,PartDataAttributes,QueuedJobData,QueuedJob,QueuedJobDataAttributes" // Comma-separated list of models to skip validation for
- ]
- ignoreFileOverride = "${projectDir}/.openapi-generator-ignore"
-}
-
-sourceSets {
- main {
- java {
- srcDirs += [
- "${buildDir}/generated/src/main/java"
- ]
- }
- }
-}
-tasks.named("openApiGenerate").configure {
- dependsOn(":openapi-generator-plugin:build")
-}
-
-compileJava.dependsOn tasks.openApiGenerate
-compileTestJava.dependsOn tasks.openApiGenerate
-
-// Ensure our custom generator plugin is built before we generate
-tasks.openApiGenerate.dependsOn ':openapi-generator-plugin:build'
-
-java {
- withJavadocJar()
- withSourcesJar()
-}
-
-tasks.named('sourcesJar') {
- dependsOn tasks.openApiGenerate
- from sourceSets.main.allJava
- duplicatesStrategy = DuplicatesStrategy.EXCLUDE
-}
-
-jar {
- from sourceSets.main.allSource
- duplicatesStrategy = DuplicatesStrategy.EXCLUDE
-}
-
+// Override publication configuration for this specific library
publishing {
publications {
maven(MavenPublication) {
artifactId = 'api-client-java'
- from components.java
pom {
name = 'com.vertexvis:api-client-java'
description = 'The Vertex REST API client for Java.'
- url = 'https://github.com/Vertexvis/vertex-api-client-java'
- licenses {
- license {
- name = 'MIT'
- url = 'https://github.com/Vertexvis/vertex-api-client-java/blob/main/LICENSE'
- }
- }
- developers {
- developer {
- email = 'support@vertexvis.com'
- name = 'Vertex Developers'
- organizationUrl = 'https://developer.vertexvis.com/'
- }
- }
- scm {
- connection = 'scm:git:git@github.com:vertexvis/vertex-api-client-java.git'
- developerConnection = 'scm:git:git@github.com:vertexvis/vertex-api-client-java.git'
- url = 'https://github.com/Vertexvis/vertex-api-client-java'
- }
}
}
}
}
-signing {
- def hasSigningKey = project.hasProperty("signingKey")
- def hasSigningPassword = project.hasProperty("signingPassword")
- required { hasSigningKey && hasSigningPassword && !project.version.endsWith("-SNAPSHOT") }
- if (hasSigningKey && hasSigningPassword) {
- def base64Decode = { prop ->
- return new String(Base64.getDecoder().decode(project.findProperty(prop).toString())).trim()
- }
- useInMemoryPgpKeys(base64Decode("signingKey"), base64Decode("signingPassword"))
- }
- sign publishing.publications.maven
+// Ensure proper task dependencies for generated sources
+tasks.named('sourcesJar') {
+ dependsOn tasks.openApiGenerate
+}
+
+tasks.named('javadocJar') {
+ dependsOn tasks.openApiGenerate
}
javadoc {
@@ -140,12 +51,3 @@ javadoc {
}
dependsOn tasks.openApiGenerate
}
-
-tasks.withType(Sign) {
- dependsOn tasks.withType(GenerateModuleMetadata)
- dependsOn tasks.withType(Jar)
-}
-
-tasks.withType(PublishToMavenLocal) {
- dependsOn tasks.withType(Sign)
-}
diff --git a/build.gradle b/build.gradle
index 1c1adca4..daa9f223 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,48 +1,27 @@
plugins {
id "idea"
- id "io.github.gradle-nexus.publish-plugin" version "1.1.0"
+ id "io.github.gradle-nexus.publish-plugin"
}
-group = 'com.vertexvis'
-version = '0.11.0'
+def projectVersion = '0.11.0'
+def isSnapshot = project.hasProperty('isSnapshot') && project.isSnapshot.toBoolean()
+version = isSnapshot ? "${projectVersion}-SNAPSHOT" : projectVersion
allprojects {
group = 'com.vertexvis'
- version = '0.11.0'
-
- repositories {
- mavenCentral()
- }
-}
+ version = rootProject.version
-subprojects {
- apply plugin: 'java-library'
-
- java {
- toolchain {
- languageVersion.set(JavaLanguageVersion.of(17))
- }
- }
-
- test {
- useJUnitPlatform()
- testLogging {
- events "passed", "skipped", "failed"
- }
- }
+ // Make project version accessible as a project property for buildscript blocks
+ ext.projectVersion = projectVersion
}
nexusPublishing {
repositories {
sonatype {
- nexusUrl.set(uri("https://s01.oss.sonatype.org/service/local/"))
- snapshotRepositoryUrl.set(uri("https://s01.oss.sonatype.org/content/repositories/snapshots/"))
- username = project.hasProperty("ossrhUsername") ? project.ossrhUsername : ""
- password = project.hasProperty("ossrhPassword") ? project.ossrhPassword : ""
+ nexusUrl.set(uri("https://ossrh-staging-api.central.sonatype.com/service/local/"))
+ snapshotRepositoryUrl.set(uri("https://central.sonatype.com/repository/maven-snapshots/"))
+ username = project.findProperty("ossrhUsername") ?: System.getenv("OSSRH_USERNAME")
+ password = project.findProperty("ossrhPassword") ?: System.getenv("OSSRH_PASSWORD")
}
}
}
-
-def base64Decode(prop) {
- return new String(Base64.getDecoder().decode(project.findProperty(prop).toString())).trim()
-}
diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle
new file mode 100644
index 00000000..d3ce4e01
--- /dev/null
+++ b/buildSrc/build.gradle
@@ -0,0 +1,15 @@
+plugins {
+ id 'groovy-gradle-plugin'
+}
+
+repositories {
+ gradlePluginPortal()
+ mavenCentral()
+}
+
+dependencies {
+ implementation 'org.openapitools:openapi-generator-gradle-plugin:7.14.0'
+ implementation 'org.openapitools:openapi-generator:7.14.0'
+ implementation 'io.swagger.core.v3:swagger-models:2.2.31'
+ implementation 'io.github.gradle-nexus:publish-plugin:2.0.0'
+}
diff --git a/buildSrc/src/main/groovy/vertex.java-conventions.gradle b/buildSrc/src/main/groovy/vertex.java-conventions.gradle
new file mode 100644
index 00000000..ad82efb2
--- /dev/null
+++ b/buildSrc/src/main/groovy/vertex.java-conventions.gradle
@@ -0,0 +1,79 @@
+plugins {
+ id 'java-library'
+ id 'maven-publish'
+ id 'signing'
+}
+
+java {
+ toolchain {
+ languageVersion = JavaLanguageVersion.of(17)
+ }
+ withJavadocJar()
+ withSourcesJar()
+}
+
+test {
+ useJUnitPlatform()
+}
+
+repositories {
+ mavenLocal()
+ mavenCentral()
+}
+
+publishing {
+ repositories {
+ maven {
+ name = 'GitHubPackages'
+ url = uri("https://maven.pkg.github.com/Vertexvis/vertex-api-client-java")
+ credentials {
+ username = System.getenv("GITHUB_ACTOR") ?: project.findProperty("gpr.user") ?: ""
+ password = System.getenv("GITHUB_TOKEN") ?: project.findProperty("gpr.key") ?: ""
+ }
+ }
+ }
+ publications {
+ maven(MavenPublication) {
+ from components.java
+ pom {
+ url = 'https://github.com/Vertexvis/vertex-api-client-java'
+ licenses {
+ license {
+ name = 'MIT'
+ url = 'https://github.com/Vertexvis/vertex-api-client-java/blob/main/LICENSE'
+ }
+ }
+ developers {
+ developer {
+ email = 'support@vertexvis.com'
+ name = 'Vertex Developers'
+ organizationUrl = 'https://developer.vertexvis.com/'
+ }
+ }
+ scm {
+ connection = 'scm:git:git@github.com:vertexvis/vertex-api-client-java.git'
+ developerConnection = 'scm:git:git@github.com:vertexvis/vertex-api-client-java.git'
+ url = 'https://github.com/Vertexvis/vertex-api-client-java'
+ }
+ }
+ }
+ }
+}
+
+signing {
+ def hasSigningKey = project.hasProperty("signingKey")
+ def hasSigningPassword = project.hasProperty("signingPassword")
+ required { hasSigningKey && hasSigningPassword && !project.version.endsWith("-SNAPSHOT") }
+ if (hasSigningKey && hasSigningPassword) {
+ def base64Decode = { prop ->
+ return new String(Base64.getDecoder().decode(project.findProperty(prop).toString())).trim()
+ }
+ useInMemoryPgpKeys(base64Decode("signingKey"), base64Decode("signingPassword"))
+ }
+ sign publishing.publications.maven
+}
+
+tasks.withType(Sign) {
+ dependsOn tasks.withType(GenerateModuleMetadata)
+ dependsOn tasks.withType(Jar)
+}
diff --git a/buildSrc/src/main/groovy/vertex.openapi-generator.gradle b/buildSrc/src/main/groovy/vertex.openapi-generator.gradle
new file mode 100644
index 00000000..20b0530c
--- /dev/null
+++ b/buildSrc/src/main/groovy/vertex.openapi-generator.gradle
@@ -0,0 +1,44 @@
+plugins {
+ id 'org.openapi.generator'
+}
+
+repositories {
+ mavenLocal()
+ mavenCentral()
+}
+
+openApiGenerate {
+ verbose = false
+ generatorName = 'vertex-java' // Use our custom generator
+ generateModelTests = false
+ generateApiTests = false
+ generateModelDocumentation = false
+ remoteInputSpec = 'https://platform.vertexvis.com/spec'
+ outputDir = "${buildDir}/generated/"
+ invokerPackage = 'com.vertexvis'
+ modelPackage = 'com.vertexvis.model'
+ apiPackage = 'com.vertexvis.api'
+ templateDir = "${project.rootDir}/buildSrc/src/main/resources/vertex-java"
+ configOptions = [
+ openApiNullable: "false",
+ dateLibrary: "java8",
+ hideGenerationTimestamp: "true",
+ useRuntimeException: "true",
+ ]
+ additionalProperties = [
+ skipValidationFor: "Part,PartData,PartDataAttributes,QueuedJobData,QueuedJob,QueuedJobDataAttributes" // Comma-separated list of models to skip validation for
+ ]
+ ignoreFileOverride = "${projectDir}/.openapi-generator-ignore"
+}
+
+// Ensure generated sources are included in the source sets
+sourceSets {
+ main {
+ java {
+ srcDir "${buildDir}/generated/src/main/java"
+ }
+ }
+}
+
+// Make sure compilation depends on code generation
+compileJava.dependsOn tasks.openApiGenerate
diff --git a/buildSrc/src/main/java/com/vertexvis/codegen/VertexJavaClientCodegen.java b/buildSrc/src/main/java/com/vertexvis/codegen/VertexJavaClientCodegen.java
new file mode 100644
index 00000000..b7ee2605
--- /dev/null
+++ b/buildSrc/src/main/java/com/vertexvis/codegen/VertexJavaClientCodegen.java
@@ -0,0 +1,99 @@
+package com.vertexvis.codegen;
+
+import org.openapitools.codegen.CodegenModel;
+import org.openapitools.codegen.CodegenProperty;
+import org.openapitools.codegen.languages.JavaClientCodegen;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.openapitools.codegen.CodegenModel;
+import org.openapitools.codegen.CodegenConfig;
+import org.openapitools.codegen.model.ModelsMap;
+import org.openapitools.codegen.model.ModelMap;
+import io.swagger.v3.oas.models.media.Schema;
+import java.util.*;
+
+/**
+ * Custom Java client codegen that supports conditional validation skipping.
+ */
+public class VertexJavaClientCodegen extends JavaClientCodegen {
+ private static final Logger LOGGER = LoggerFactory.getLogger(VertexJavaClientCodegen.class);
+
+ /** Configuration option key for specifying models to skip validation for. */
+ public static final String SKIP_VALIDATION_FOR = "skipValidationFor";
+ /** Vendor extension key used to mark models that should skip validation. */
+ public static final String X_SKIP_VALIDATION = "x-skip-validation";
+
+ private Set skipValidationModels = new HashSet<>();
+
+ /**
+ * Default constructor that sets up the custom CLI options.
+ */
+ public VertexJavaClientCodegen() {
+ super();
+
+ // Add custom CLI option
+ cliOptions.add(org.openapitools.codegen.CliOption.newString(SKIP_VALIDATION_FOR,
+ "Comma-separated list of model class names to skip validation for (e.g., Scene,SceneMetadata)"));
+ }
+
+ @Override
+ public String getName() {
+ return "vertex-java";
+ }
+
+ @Override
+ public String getHelp() {
+ return "Generates a Vertex-customized Java client library.";
+ }
+
+ @Override
+ public void processOpts() {
+ super.processOpts();
+
+ // Parse the skipValidationFor option
+ if (additionalProperties.containsKey(SKIP_VALIDATION_FOR)) {
+ String skipValidationForValue = (String) additionalProperties.get(SKIP_VALIDATION_FOR);
+ if (skipValidationForValue != null && !skipValidationForValue.trim().isEmpty()) {
+ String[] modelNames = skipValidationForValue.split(",");
+ for (String modelName : modelNames) {
+ skipValidationModels.add(modelName.trim());
+ }
+ LOGGER.info("Models to skip validation: {}", skipValidationModels);
+ }
+ }
+ }
+
+ @Override
+ public CodegenModel fromModel(String name, Schema schema) {
+ CodegenModel model = super.fromModel(name, schema);
+
+ // Check if this model should skip validation
+ if (skipValidationModels.contains(model.classname)) {
+ LOGGER.info("Adding skip validation extension for model: {}", model.classname);
+ model.vendorExtensions.put(X_SKIP_VALIDATION, true);
+ }
+
+ return model;
+ }
+
+ @Override
+ public ModelsMap postProcessModels(ModelsMap objs) {
+ var result = super.postProcessModels(objs);
+
+ // Additional processing can be done here if needed
+ @SuppressWarnings("unchecked")
+ List models = (List) objs.get("models");
+
+ if (models != null) {
+ for (ModelMap modelMap : models) {
+ CodegenModel model = modelMap.getModel();
+
+ if (model.vendorExtensions.containsKey(X_SKIP_VALIDATION)) {
+ LOGGER.debug("Model {} will skip validation", model.classname);
+ }
+ }
+ }
+
+ return result;
+ }
+}
diff --git a/buildSrc/src/main/resources/META-INF/services/org.openapitools.codegen.CodegenConfig b/buildSrc/src/main/resources/META-INF/services/org.openapitools.codegen.CodegenConfig
new file mode 100644
index 00000000..953e26e4
--- /dev/null
+++ b/buildSrc/src/main/resources/META-INF/services/org.openapitools.codegen.CodegenConfig
@@ -0,0 +1 @@
+com.vertexvis.codegen.VertexJavaClientCodegen
diff --git a/buildSrc/src/main/resources/vertex-java/pojo.mustache b/buildSrc/src/main/resources/vertex-java/pojo.mustache
new file mode 100644
index 00000000..e1f16c3b
--- /dev/null
+++ b/buildSrc/src/main/resources/vertex-java/pojo.mustache
@@ -0,0 +1,601 @@
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParseException;
+import com.google.gson.TypeAdapterFactory;
+import com.google.gson.reflect.TypeToken;
+import com.google.gson.TypeAdapter;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonWriter;
+import java.io.IOException;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import {{invokerPackage}}.JSON;
+
+/**
+ * {{description}}{{^description}}{{classname}}{{/description}}{{#isDeprecated}}
+ * @deprecated{{/isDeprecated}}
+ */{{#isDeprecated}}
+@Deprecated{{/isDeprecated}}
+{{#swagger1AnnotationLibrary}}
+{{#description}}
+@ApiModel(description = "{{{.}}}")
+{{/description}}
+{{/swagger1AnnotationLibrary}}
+{{#swagger2AnnotationLibrary}}
+{{#description}}
+@Schema(description = "{{{.}}}")
+{{/description}}
+{{/swagger2AnnotationLibrary}}
+{{>additionalModelTypeAnnotations}}{{>generatedAnnotation}}{{#discriminator}}{{>typeInfoAnnotation}}{{/discriminator}}{{>xmlAnnotation}}
+{{#vendorExtensions.x-class-extra-annotation}}
+{{{vendorExtensions.x-class-extra-annotation}}}
+{{/vendorExtensions.x-class-extra-annotation}}
+public class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{{#vendorExtensions.x-implements}}{{#-first}}implements {{{.}}}{{/-first}}{{^-first}}, {{{.}}}{{/-first}}{{#-last}} {{/-last}}{{/vendorExtensions.x-implements}}{
+{{#serializableModel}}
+ private static final long serialVersionUID = 1L;
+
+{{/serializableModel}}
+ {{#vars}}
+ {{#isEnum}}
+ {{^isContainer}}
+{{>modelInnerEnum}}
+
+ {{/isContainer}}
+ {{#isContainer}}
+ {{#mostInnerItems}}
+{{>modelInnerEnum}}
+
+ {{/mostInnerItems}}
+ {{/isContainer}}
+ {{/isEnum}}
+ public static final String SERIALIZED_NAME_{{nameInSnakeCase}} = "{{baseName}}";
+ {{#withXml}}
+ @Xml{{#isXmlAttribute}}Attribute{{/isXmlAttribute}}{{^isXmlAttribute}}Element{{/isXmlAttribute}}(name = "{{items.xmlName}}{{^items.xmlName}}{{xmlName}}{{^xmlName}}{{baseName}}{{/xmlName}}{{/items.xmlName}}"{{#xmlNamespace}}, namespace = "{{.}}"{{/xmlNamespace}})
+ {{#isXmlWrapped}}
+ @XmlElementWrapper(name = "{{xmlName}}{{^xmlName}}{{baseName}}{{/xmlName}}"{{#xmlNamespace}}, namespace = "{{.}}"{{/xmlNamespace}})
+ {{/isXmlWrapped}}
+ {{/withXml}}
+ {{#deprecated}}
+ @Deprecated
+ {{/deprecated}}
+ @SerializedName(SERIALIZED_NAME_{{nameInSnakeCase}})
+ {{#vendorExtensions.x-field-extra-annotation}}
+ {{{vendorExtensions.x-field-extra-annotation}}}
+ {{/vendorExtensions.x-field-extra-annotation}}
+ {{>nullable_var_annotations}}{{! prevent indent}}
+ {{#isDiscriminator}}protected{{/isDiscriminator}}{{^isDiscriminator}}private{{/isDiscriminator}} {{{datatypeWithEnum}}} {{name}}{{#defaultValue}} = {{{.}}}{{/defaultValue}};
+
+ {{/vars}}
+ public {{classname}}() {
+ {{#parent}}
+ {{#parcelableModel}}
+ super();
+ {{/parcelableModel}}
+ {{/parent}}
+ {{#discriminator}}
+ {{#discriminator.isEnum}}
+{{#readWriteVars}}{{#isDiscriminator}}{{#defaultValue}}
+ this.{{name}} = {{defaultValue}};
+{{/defaultValue}}{{/isDiscriminator}}{{/readWriteVars}}
+ {{/discriminator.isEnum}}
+ {{^discriminator.isEnum}}
+ this.{{{discriminatorName}}} = this.getClass().getSimpleName();
+ {{/discriminator.isEnum}}
+ {{/discriminator}}
+ }
+ {{#vendorExtensions.x-has-readonly-properties}}
+ {{^withXml}}
+
+ public {{classname}}(
+ {{#readOnlyVars}}
+ {{{datatypeWithEnum}}} {{name}}{{^-last}}, {{/-last}}
+ {{/readOnlyVars}}
+ ) {
+ this();
+ {{#readOnlyVars}}
+ this.{{name}} = {{name}};
+ {{/readOnlyVars}}
+ }
+ {{/withXml}}
+ {{/vendorExtensions.x-has-readonly-properties}}
+ {{#vars}}
+
+ {{^isReadOnly}}
+ {{#deprecated}}
+ @Deprecated
+ {{/deprecated}}
+ public {{classname}} {{name}}({{>nullable_var_annotations}} {{{datatypeWithEnum}}} {{name}}) {
+ this.{{name}} = {{name}};
+ return this;
+ }
+ {{#isArray}}
+
+ public {{classname}} add{{nameInPascalCase}}Item({{{items.datatypeWithEnum}}} {{name}}Item) {
+ if (this.{{name}} == null) {
+ this.{{name}} = {{{defaultValue}}}{{^defaultValue}}new {{#uniqueItems}}LinkedHashSet{{/uniqueItems}}{{^uniqueItems}}ArrayList{{/uniqueItems}}<>(){{/defaultValue}};
+ }
+ this.{{name}}.add({{name}}Item);
+ return this;
+ }
+ {{/isArray}}
+ {{#isMap}}
+
+ public {{classname}} put{{nameInPascalCase}}Item(String key, {{{items.datatypeWithEnum}}} {{name}}Item) {
+ if (this.{{name}} == null) {
+ this.{{name}} = {{{defaultValue}}}{{^defaultValue}}new HashMap<>(){{/defaultValue}};
+ }
+ this.{{name}}.put(key, {{name}}Item);
+ return this;
+ }
+ {{/isMap}}
+
+ {{/isReadOnly}}
+ /**
+ {{#description}}
+ * {{.}}
+ {{/description}}
+ {{^description}}
+ * Get {{name}}
+ {{/description}}
+ {{#minimum}}
+ * minimum: {{.}}
+ {{/minimum}}
+ {{#maximum}}
+ * maximum: {{.}}
+ {{/maximum}}
+ * @return {{name}}
+ {{#deprecated}}
+ * @deprecated
+ {{/deprecated}}
+ */
+{{#deprecated}}
+ @Deprecated
+{{/deprecated}}
+ {{>nullable_var_annotations}}{{! prevent indent}}
+{{#useBeanValidation}}
+{{>beanValidation}}
+
+{{/useBeanValidation}}
+{{#swagger1AnnotationLibrary}}
+ @ApiModelProperty({{#example}}example = "{{{.}}}", {{/example}}{{#required}}required = {{required}}, {{/required}}value = "{{{description}}}")
+{{/swagger1AnnotationLibrary}}
+{{#swagger2AnnotationLibrary}}
+ @Schema({{#example}}example = "{{{.}}}", {{/example}}requiredMode = {{#required}}Schema.RequiredMode.REQUIRED{{/required}}{{^required}}Schema.RequiredMode.NOT_REQUIRED{{/required}}, description = "{{{description}}}")
+{{/swagger2AnnotationLibrary}}
+{{#vendorExtensions.x-extra-annotation}}
+ {{{vendorExtensions.x-extra-annotation}}}
+{{/vendorExtensions.x-extra-annotation}}
+ public {{{datatypeWithEnum}}} {{getter}}() {
+ return {{name}};
+ }
+
+ {{^isReadOnly}}
+{{#vendorExtensions.x-setter-extra-annotation}} {{{vendorExtensions.x-setter-extra-annotation}}}
+{{/vendorExtensions.x-setter-extra-annotation}}{{#deprecated}} @Deprecated
+{{/deprecated}} public void {{setter}}({{>nullable_var_annotations}} {{{datatypeWithEnum}}} {{name}}) {
+ this.{{name}} = {{name}};
+ }
+ {{/isReadOnly}}
+
+ {{/vars}}
+{{>libraries/okhttp-gson/additional_properties}}
+
+
+ @Override
+ public boolean equals(Object o) {
+ {{#useReflectionEqualsHashCode}}
+ return EqualsBuilder.reflectionEquals(this, o, false, null, true);
+ {{/useReflectionEqualsHashCode}}
+ {{^useReflectionEqualsHashCode}}
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }{{#hasVars}}
+ {{classname}} {{classVarName}} = ({{classname}}) o;
+ return {{#vars}}{{#isByteArray}}Arrays{{/isByteArray}}{{^isByteArray}}Objects{{/isByteArray}}.equals(this.{{name}}, {{classVarName}}.{{name}}){{^-last}} &&
+ {{/-last}}{{/vars}}{{#isAdditionalPropertiesTrue}}&&
+ Objects.equals(this.additionalProperties, {{classVarName}}.additionalProperties){{/isAdditionalPropertiesTrue}}{{#parent}} &&
+ super.equals(o){{/parent}};{{/hasVars}}{{^hasVars}}
+ return {{#parent}}super.equals(o){{/parent}}{{^parent}}true{{/parent}};{{/hasVars}}
+ {{/useReflectionEqualsHashCode}}
+ }{{#vendorExtensions.x-jackson-optional-nullable-helpers}}
+
+ private static boolean equalsNullable(JsonNullable a, JsonNullable b) {
+ return a == b || (a != null && b != null && a.isPresent() && b.isPresent() && Objects.deepEquals(a.get(), b.get()));
+ }{{/vendorExtensions.x-jackson-optional-nullable-helpers}}
+
+ @Override
+ public int hashCode() {
+ {{#useReflectionEqualsHashCode}}
+ return HashCodeBuilder.reflectionHashCode(this);
+ {{/useReflectionEqualsHashCode}}
+ {{^useReflectionEqualsHashCode}}
+ return Objects.hash({{#vars}}{{^isByteArray}}{{name}}{{/isByteArray}}{{#isByteArray}}Arrays.hashCode({{name}}){{/isByteArray}}{{^-last}}, {{/-last}}{{/vars}}{{#parent}}{{#hasVars}}, {{/hasVars}}super.hashCode(){{/parent}}{{#isAdditionalPropertiesTrue}}{{#hasVars}}, {{/hasVars}}{{^hasVars}}{{#parent}}, {{/parent}}{{/hasVars}}additionalProperties{{/isAdditionalPropertiesTrue}});
+ {{/useReflectionEqualsHashCode}}
+ }{{#vendorExtensions.x-jackson-optional-nullable-helpers}}
+
+ private static int hashCodeNullable(JsonNullable a) {
+ if (a == null) {
+ return 1;
+ }
+ return a.isPresent() ? Arrays.deepHashCode(new Object[]{a.get()}) : 31;
+ }{{/vendorExtensions.x-jackson-optional-nullable-helpers}}
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("class {{classname}} {\n");
+ {{#parent}}
+ sb.append(" ").append(toIndentedString(super.toString())).append("\n");
+ {{/parent}}
+ {{#vars}}
+ sb.append(" {{name}}: ").append({{#isPassword}}"*"{{/isPassword}}{{^isPassword}}toIndentedString({{name}}){{/isPassword}}).append("\n");
+ {{/vars}}
+{{#isAdditionalPropertiesTrue}}
+ sb.append(" additionalProperties: ").append(toIndentedString(additionalProperties)).append("\n");
+{{/isAdditionalPropertiesTrue}}
+ sb.append("}");
+ return sb.toString();
+ }
+
+ /**
+ * Convert the given object to string with each line indented by 4 spaces
+ * (except the first line).
+ */
+ private String toIndentedString(Object o) {
+ if (o == null) {
+ return "null";
+ }
+ return o.toString().replace("\n", "\n ");
+ }
+
+{{#parcelableModel}}
+
+ public void writeToParcel(Parcel out, int flags) {
+{{#model}}
+{{#isArray}}
+ out.writeList(this);
+{{/isArray}}
+{{^isArray}}
+{{#parent}}
+ super.writeToParcel(out, flags);
+{{/parent}}
+{{#vars}}
+ out.writeValue({{name}});
+{{/vars}}
+{{/isArray}}
+{{/model}}
+ }
+
+ {{classname}}(Parcel in) {
+{{#isArray}}
+ in.readTypedList(this, {{arrayModelType}}.CREATOR);
+{{/isArray}}
+{{^isArray}}
+{{#parent}}
+ super(in);
+{{/parent}}
+{{#vars}}
+{{#isPrimitiveType}}
+ {{name}} = ({{{datatypeWithEnum}}})in.readValue(null);
+{{/isPrimitiveType}}
+{{^isPrimitiveType}}
+ {{name}} = ({{{datatypeWithEnum}}})in.readValue({{complexType}}.class.getClassLoader());
+{{/isPrimitiveType}}
+{{/vars}}
+{{/isArray}}
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final Parcelable.Creator<{{classname}}> CREATOR = new Parcelable.Creator<{{classname}}>() {
+ public {{classname}} createFromParcel(Parcel in) {
+{{#model}}
+{{#isArray}}
+ {{classname}} result = new {{classname}}();
+ result.addAll(in.readArrayList({{arrayModelType}}.class.getClassLoader()));
+ return result;
+{{/isArray}}
+{{^isArray}}
+ return new {{classname}}(in);
+{{/isArray}}
+{{/model}}
+ }
+ public {{classname}}[] newArray(int size) {
+ return new {{classname}}[size];
+ }
+ };
+{{/parcelableModel}}
+
+ public static HashSet openapiFields;
+ public static HashSet openapiRequiredFields;
+
+ static {
+ // a set of all properties/fields (JSON key names)
+ {{#hasVars}}
+ openapiFields = new HashSet(Arrays.asList({{#allVars}}"{{baseName}}"{{^-last}}, {{/-last}}{{/allVars}}));
+ {{/hasVars}}
+ {{^hasVars}}
+ openapiFields = new HashSet(0);
+ {{/hasVars}}
+
+ // a set of required properties/fields (JSON key names)
+ {{#hasRequired}}
+ openapiRequiredFields = new HashSet(Arrays.asList({{#requiredVars}}"{{baseName}}"{{^-last}}, {{/-last}}{{/requiredVars}}));
+ {{/hasRequired}}
+ {{^hasRequired}}
+ openapiRequiredFields = new HashSet(0);
+ {{/hasRequired}}
+ }
+
+ /**
+ * Validates the JSON Element and throws an exception if issues found
+ *
+ * @param jsonElement JSON Element
+ * @throws IOException if the JSON Element is invalid with respect to {{classname}}
+ */
+ public static void validateJsonElement(JsonElement jsonElement) throws IOException {
+ if (jsonElement == null) {
+ if (!{{classname}}.openapiRequiredFields.isEmpty()) { // has required fields but JSON element is null
+ throw new IllegalArgumentException(String.format("The required field(s) %s in {{{classname}}} is not found in the empty JSON string", {{classname}}.openapiRequiredFields.toString()));
+ }
+ }
+ {{^hasChildren}}
+ {{^isAdditionalPropertiesTrue}}
+
+ Set> entries = jsonElement.getAsJsonObject().entrySet();
+ // check to see if the JSON string contains additional fields
+ for (Map.Entry entry : entries) {
+ if (!{{classname}}.openapiFields.contains(entry.getKey())) {
+ throw new IllegalArgumentException(String.format("The field `%s` in the JSON string is not defined in the `{{classname}}` properties. JSON: %s", entry.getKey(), jsonElement.toString()));
+ }
+ }
+ {{/isAdditionalPropertiesTrue}}
+ {{#requiredVars}}
+ {{#-first}}
+
+ // check to make sure all required properties/fields are present in the JSON string
+ for (String requiredField : {{classname}}.openapiRequiredFields) {
+ if (jsonElement.getAsJsonObject().get(requiredField) == null) {
+ throw new IllegalArgumentException(String.format("The required field `%s` is not found in the JSON string: %s", requiredField, jsonElement.toString()));
+ }
+ }
+ {{/-first}}
+ {{/requiredVars}}
+ {{/hasChildren}}
+ {{^discriminator}}
+ {{#hasVars}}
+ JsonObject jsonObj = jsonElement.getAsJsonObject();
+ {{/hasVars}}
+ {{#vars}}
+ {{#isArray}}
+ {{#items.isModel}}
+ {{#required}}
+ // ensure the json data is an array
+ if (!jsonObj.get("{{{baseName}}}").isJsonArray()) {
+ throw new IllegalArgumentException(String.format("Expected the field `{{{baseName}}}` to be an array in the JSON string but got `%s`", jsonObj.get("{{{baseName}}}").toString()));
+ }
+
+ JsonArray jsonArray{{name}} = jsonObj.getAsJsonArray("{{{baseName}}}");
+ // validate the required field `{{{baseName}}}` (array)
+ for (int i = 0; i < jsonArray{{name}}.size(); i++) {
+ {{{items.dataType}}}.validateJsonElement(jsonArray{{name}}.get(i));
+ };
+ {{/required}}
+ {{^required}}
+ if (jsonObj.get("{{{baseName}}}") != null && !jsonObj.get("{{{baseName}}}").isJsonNull()) {
+ JsonArray jsonArray{{name}} = jsonObj.getAsJsonArray("{{{baseName}}}");
+ if (jsonArray{{name}} != null) {
+ // ensure the json data is an array
+ if (!jsonObj.get("{{{baseName}}}").isJsonArray()) {
+ throw new IllegalArgumentException(String.format("Expected the field `{{{baseName}}}` to be an array in the JSON string but got `%s`", jsonObj.get("{{{baseName}}}").toString()));
+ }
+
+ // validate the optional field `{{{baseName}}}` (array)
+ for (int i = 0; i < jsonArray{{name}}.size(); i++) {
+ {{{items.dataType}}}.validateJsonElement(jsonArray{{name}}.get(i));
+ };
+ }
+ }
+ {{/required}}
+ {{/items.isModel}}
+ {{^items.isModel}}
+ {{^required}}
+ // ensure the optional json data is an array if present
+ if (jsonObj.get("{{{baseName}}}") != null && !jsonObj.get("{{{baseName}}}").isJsonNull() && !jsonObj.get("{{{baseName}}}").isJsonArray()) {
+ throw new IllegalArgumentException(String.format("Expected the field `{{{baseName}}}` to be an array in the JSON string but got `%s`", jsonObj.get("{{{baseName}}}").toString()));
+ }
+ {{/required}}
+ {{#required}}
+ // ensure the required json array is present
+ if (jsonObj.get("{{{baseName}}}") == null) {
+ throw new IllegalArgumentException("Expected the field `linkedContent` to be an array in the JSON string but got `null`");
+ } else if (!jsonObj.get("{{{baseName}}}").isJsonArray()) {
+ throw new IllegalArgumentException(String.format("Expected the field `{{{baseName}}}` to be an array in the JSON string but got `%s`", jsonObj.get("{{{baseName}}}").toString()));
+ }
+ {{/required}}
+ {{/items.isModel}}
+ {{/isArray}}
+ {{^isContainer}}
+ {{#isString}}
+ if ({{#notRequiredOrIsNullable}}(jsonObj.get("{{{baseName}}}") != null && !jsonObj.get("{{{baseName}}}").isJsonNull()) && {{/notRequiredOrIsNullable}}!jsonObj.get("{{{baseName}}}").isJsonPrimitive()) {
+ throw new IllegalArgumentException(String.format("Expected the field `{{{baseName}}}` to be a primitive type in the JSON string but got `%s`", jsonObj.get("{{{baseName}}}").toString()));
+ }
+ {{/isString}}
+ {{#isModel}}
+ {{#required}}
+ // validate the required field `{{{baseName}}}`
+ {{{dataType}}}.validateJsonElement(jsonObj.get("{{{baseName}}}"));
+ {{/required}}
+ {{^required}}
+ // validate the optional field `{{{baseName}}}`
+ if (jsonObj.get("{{{baseName}}}") != null && !jsonObj.get("{{{baseName}}}").isJsonNull()) {
+ {{{dataType}}}.validateJsonElement(jsonObj.get("{{{baseName}}}"));
+ }
+ {{/required}}
+ {{/isModel}}
+ {{#isEnum}}
+ {{#required}}
+ // validate the required field `{{{baseName}}}`
+ {{{datatypeWithEnum}}}.validateJsonElement(jsonObj.get("{{{baseName}}}"));
+ {{/required}}
+ {{^required}}
+ // validate the optional field `{{{baseName}}}`
+ if (jsonObj.get("{{{baseName}}}") != null && !jsonObj.get("{{{baseName}}}").isJsonNull()) {
+ {{{datatypeWithEnum}}}.validateJsonElement(jsonObj.get("{{{baseName}}}"));
+ }
+ {{/required}}
+ {{/isEnum}}
+ {{#isEnumRef}}
+ {{#required}}
+ // validate the required field `{{{baseName}}}`
+ {{{dataType}}}.validateJsonElement(jsonObj.get("{{{baseName}}}"));
+ {{/required}}
+ {{^required}}
+ // validate the optional field `{{{baseName}}}`
+ if (jsonObj.get("{{{baseName}}}") != null && !jsonObj.get("{{{baseName}}}").isJsonNull()) {
+ {{{dataType}}}.validateJsonElement(jsonObj.get("{{{baseName}}}"));
+ }
+ {{/required}}
+ {{/isEnumRef}}
+ {{/isContainer}}
+ {{/vars}}
+ {{/discriminator}}
+ {{#hasChildren}}
+ {{#discriminator}}
+
+ String discriminatorValue = jsonElement.getAsJsonObject().get("{{{propertyBaseName}}}").getAsString();
+ switch (discriminatorValue) {
+ {{#mappedModels}}
+ case "{{mappingName}}":
+ {{modelName}}.validateJsonElement(jsonElement);
+ break;
+ {{/mappedModels}}
+ default:
+ throw new IllegalArgumentException(String.format("The value of the `{{{propertyBaseName}}}` field `%s` does not match any key defined in the discriminator's mapping.", discriminatorValue));
+ }
+ {{/discriminator}}
+ {{/hasChildren}}
+ }
+
+{{^hasChildren}}
+ public static class CustomTypeAdapterFactory implements TypeAdapterFactory {
+ @SuppressWarnings("unchecked")
+ @Override
+ public TypeAdapter create(Gson gson, TypeToken type) {
+ if (!{{classname}}.class.isAssignableFrom(type.getRawType())) {
+ return null; // this class only serializes '{{classname}}' and its subtypes
+ }
+ final TypeAdapter elementAdapter = gson.getAdapter(JsonElement.class);
+ final TypeAdapter<{{classname}}> thisAdapter
+ = gson.getDelegateAdapter(this, TypeToken.get({{classname}}.class));
+
+ return (TypeAdapter) new TypeAdapter<{{classname}}>() {
+ @Override
+ public void write(JsonWriter out, {{classname}} value) throws IOException {
+ JsonObject obj = thisAdapter.toJsonTree(value).getAsJsonObject();
+ {{#isAdditionalPropertiesTrue}}
+ obj.remove("additionalProperties");
+ // serialize additional properties
+ if (value.getAdditionalProperties() != null) {
+ for (Map.Entry entry : value.getAdditionalProperties().entrySet()) {
+ if (entry.getValue() instanceof String)
+ obj.addProperty(entry.getKey(), (String) entry.getValue());
+ else if (entry.getValue() instanceof Number)
+ obj.addProperty(entry.getKey(), (Number) entry.getValue());
+ else if (entry.getValue() instanceof Boolean)
+ obj.addProperty(entry.getKey(), (Boolean) entry.getValue());
+ else if (entry.getValue() instanceof Character)
+ obj.addProperty(entry.getKey(), (Character) entry.getValue());
+ else {
+ JsonElement jsonElement = gson.toJsonTree(entry.getValue());
+ if (jsonElement.isJsonArray()) {
+ obj.add(entry.getKey(), jsonElement.getAsJsonArray());
+ } else {
+ obj.add(entry.getKey(), jsonElement.getAsJsonObject());
+ }
+ }
+ }
+ }
+ {{/isAdditionalPropertiesTrue}}
+ elementAdapter.write(out, obj);
+ }
+
+ @Override
+ public {{classname}} read(JsonReader in) throws IOException {
+ JsonElement jsonElement = elementAdapter.read(in);
+{{^vendorExtensions.x-skip-validation}}
+ validateJsonElement(jsonElement);
+{{/vendorExtensions.x-skip-validation}}
+{{#vendorExtensions.x-skip-validation}}
+ // vertex:TODO: Skipping validation during deserialization of the JSON element based on configuration
+ // validateJsonElement(jsonElement);
+{{/vendorExtensions.x-skip-validation}}
+ {{#isAdditionalPropertiesTrue}}
+ JsonObject jsonObj = jsonElement.getAsJsonObject();
+ // store additional fields in the deserialized instance
+ {{classname}} instance = thisAdapter.fromJsonTree(jsonObj);
+ for (Map.Entry entry : jsonObj.entrySet()) {
+ if (!openapiFields.contains(entry.getKey())) {
+ if (entry.getValue().isJsonPrimitive()) { // primitive type
+ if (entry.getValue().getAsJsonPrimitive().isString())
+ instance.putAdditionalProperty(entry.getKey(), entry.getValue().getAsString());
+ else if (entry.getValue().getAsJsonPrimitive().isNumber())
+ instance.putAdditionalProperty(entry.getKey(), entry.getValue().getAsNumber());
+ else if (entry.getValue().getAsJsonPrimitive().isBoolean())
+ instance.putAdditionalProperty(entry.getKey(), entry.getValue().getAsBoolean());
+ else
+ throw new IllegalArgumentException(String.format("The field `%s` has unknown primitive type. Value: %s", entry.getKey(), entry.getValue().toString()));
+ } else if (entry.getValue().isJsonArray()) {
+ instance.putAdditionalProperty(entry.getKey(), gson.fromJson(entry.getValue(), List.class));
+ } else { // JSON object
+ instance.putAdditionalProperty(entry.getKey(), gson.fromJson(entry.getValue(), HashMap.class));
+ }
+ }
+ }
+ return instance;
+ {{/isAdditionalPropertiesTrue}}
+ {{^isAdditionalPropertiesTrue}}
+ return thisAdapter.fromJsonTree(jsonElement);
+ {{/isAdditionalPropertiesTrue}}
+ }
+
+ }.nullSafe();
+ }
+ }
+{{/hasChildren}}
+
+ /**
+ * Create an instance of {{classname}} given an JSON string
+ *
+ * @param jsonString JSON string
+ * @return An instance of {{classname}}
+ * @throws IOException if the JSON string is invalid with respect to {{classname}}
+ */
+ public static {{{classname}}} fromJson(String jsonString) throws IOException {
+ return JSON.getGson().fromJson(jsonString, {{{classname}}}.class);
+ }
+
+ /**
+ * Convert an instance of {{classname}} to an JSON string
+ *
+ * @return JSON string
+ */
+ public String toJson() {
+ return JSON.getGson().toJson(this);
+ }
+}
\ No newline at end of file
diff --git a/examples/build.gradle b/examples/build.gradle
index 851083a5..8032d755 100644
--- a/examples/build.gradle
+++ b/examples/build.gradle
@@ -1,6 +1,7 @@
plugins {
id 'java'
id 'application'
+ id 'vertex.java-conventions'
}
description = 'Example applications using the Vertex API Client Library'
diff --git a/examples/src/main/java/com/vertexvis/example/CreateAssemblyFromRevisionsExample.java b/examples/src/main/java/com/vertexvis/example/CreateAssemblyFromRevisionsExample.java
index ba8841db..8a363d64 100644
--- a/examples/src/main/java/com/vertexvis/example/CreateAssemblyFromRevisionsExample.java
+++ b/examples/src/main/java/com/vertexvis/example/CreateAssemblyFromRevisionsExample.java
@@ -86,7 +86,7 @@ public void run() {
} catch (InterruptedException e) {
logger.severe(e.getMessage());
// Restore interrupted state...
- Thread.currentThread().interrupt();
+ java.lang.Thread.currentThread().interrupt();
}
}
diff --git a/examples/src/main/java/com/vertexvis/example/CreatePartRevisionsWithMetadataExample.java b/examples/src/main/java/com/vertexvis/example/CreatePartRevisionsWithMetadataExample.java
index 3798f009..88dc2ea9 100644
--- a/examples/src/main/java/com/vertexvis/example/CreatePartRevisionsWithMetadataExample.java
+++ b/examples/src/main/java/com/vertexvis/example/CreatePartRevisionsWithMetadataExample.java
@@ -87,7 +87,7 @@ public void run() {
} catch (InterruptedException e) {
logger.severe(e.getMessage());
// Restore interrupted state...
- Thread.currentThread().interrupt();
+ java.lang.Thread.currentThread().interrupt();
}
}
diff --git a/openapi-generator-plugin/build.gradle b/openapi-generator-plugin/build.gradle
index 18d4e68a..ca9b6e61 100644
--- a/openapi-generator-plugin/build.gradle
+++ b/openapi-generator-plugin/build.gradle
@@ -6,6 +6,11 @@ plugins {
description = 'Custom OpenAPI Generator Plugin for Vertex API Client'
+repositories {
+ mavenLocal()
+ mavenCentral()
+}
+
dependencies {
implementation 'org.openapitools:openapi-generator:7.14.0'
implementation 'org.openapitools:openapi-generator-gradle-plugin:7.14.0'
@@ -21,6 +26,16 @@ java {
}
publishing {
+ repositories {
+ maven {
+ name = 'GitHubPackages'
+ url = uri("https://maven.pkg.github.com/Vertexvis/vertex-api-client-java")
+ credentials {
+ username = System.getenv("GITHUB_ACTOR") ?: project.findProperty("gpr.user") ?: ""
+ password = System.getenv("GITHUB_TOKEN") ?: project.findProperty("gpr.key") ?: ""
+ }
+ }
+ }
publications {
maven(MavenPublication) {
artifactId = 'openapi-generator-plugin'
@@ -70,9 +85,9 @@ tasks.withType(Sign) {
dependsOn tasks.withType(Jar)
}
-tasks.withType(PublishToMavenLocal) {
- dependsOn tasks.withType(Sign)
-}
+// tasks.withType(PublishToMavenLocal) {
+// dependsOn tasks.withType(Sign)
+// }
// Debug task to check if the service file exists
task checkServiceFile {
@@ -85,3 +100,16 @@ task checkServiceFile {
}
}
}
+
+// Debug task to check version
+task printVersion {
+ doLast {
+ println "Project version: ${project.version}"
+ println "Root project version: ${rootProject.version}"
+ println "isSnapshot property: ${project.hasProperty('isSnapshot')}"
+ if (project.hasProperty('isSnapshot')) {
+ println "isSnapshot value: ${project.isSnapshot}"
+ }
+ println "Version ends with SNAPSHOT: ${project.version.endsWith('-SNAPSHOT')}"
+ }
+}
diff --git a/openapi-generator-plugin/src/main/java/com/vertexvis/codegen/VertexJavaClientCodegen.java b/openapi-generator-plugin/src/main/java/com/vertexvis/codegen/VertexJavaClientCodegen.java
index e3a99c52..b7ee2605 100644
--- a/openapi-generator-plugin/src/main/java/com/vertexvis/codegen/VertexJavaClientCodegen.java
+++ b/openapi-generator-plugin/src/main/java/com/vertexvis/codegen/VertexJavaClientCodegen.java
@@ -15,7 +15,7 @@
/**
* Custom Java client codegen that supports conditional validation skipping.
*/
-public class VertexJavaClientCodegen extends JavaClientCodegen implements CodegenConfig {
+public class VertexJavaClientCodegen extends JavaClientCodegen {
private static final Logger LOGGER = LoggerFactory.getLogger(VertexJavaClientCodegen.class);
/** Configuration option key for specifying models to skip validation for. */
diff --git a/scripts/bump-version.sh b/scripts/bump-version.sh
index 3cebd15f..49420d0b 100755
--- a/scripts/bump-version.sh
+++ b/scripts/bump-version.sh
@@ -17,8 +17,9 @@ main() {
new=$(_bump_version "$old" "$@")
echo "Updating version from $old to $new"
- sed -i "s|version = '$old'|version = '$new'|" build.gradle
- sed -i "s|$old|$new|" README.md
+ # Use portable sed approach that works on both macOS and Linux
+ sed "s|def projectVersion = '$old'|def projectVersion = '$new'|" build.gradle > build.gradle.tmp && mv build.gradle.tmp build.gradle
+ sed "s|$old|$new|g" README.md > README.md.tmp && mv README.md.tmp README.md
}
main "$@"
diff --git a/scripts/version-lib.sh b/scripts/version-lib.sh
index 9c38564f..b688f6c9 100644
--- a/scripts/version-lib.sh
+++ b/scripts/version-lib.sh
@@ -46,8 +46,8 @@ _die() {
#
# Returns version.
_get_version() {
- local prefix="version = "
- grep "$prefix" build.gradle | tr -d "$prefix" | tr -d "'"
+ local prefix="def projectVersion = "
+ grep "$prefix" build.gradle | sed "s/$prefix//" | tr -d "'"
}
# Internal: Bump API client version.