From 12d2d8c7f2e1e18e0bcfb447dd593a4413b83f17 Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Tue, 11 Nov 2025 19:22:14 +0000 Subject: [PATCH 01/15] support Java exporter in CI --- .github/workflows/integration.yml | 24 +++++++ .github/workflows/website.yml | 2 + package.json | 3 + src/convert.ts | 16 ++++- src/exporters/java.ts | 84 ++++++++++++++++++++++ src/java-caller.d.ts | 1 + tests/integration/convert.test.ts | 1 + tests/integration/java-app/pom.xml | 109 +++++++++++++++++++++++++++++ tests/integration/run-java.sh | 36 ++++++++++ 9 files changed, 274 insertions(+), 2 deletions(-) create mode 100644 src/exporters/java.ts create mode 100644 src/java-caller.d.ts create mode 100644 tests/integration/java-app/pom.xml create mode 100755 tests/integration/run-java.sh diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index 1566ff1..5a70ec4 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -112,3 +112,27 @@ jobs: - name: Integration tests run: | npm run test:integration-ruby + integration-tests-java: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Setup node.js + uses: actions/setup-node@v4 + - name: Install + run: | + npm install + - name: Build + run: | + npm run build + - name: Install Java + uses: actions/setup-java@v5 + with: + distribution: temurin + java-version: 21 + - name: Prepare for testing + run: | + npm run + npm run test:setup-java + - name: Integration tests + run: | + JAVA_ES_REQUEST_CONVERTER_JAR=/home/runner/work/request-converter/request-converter/tests/integration/.java-es-request-converter/build/libs/java-es-request-converter-1.0-SNAPSHOT.jar npm run test:integration-java diff --git a/.github/workflows/website.yml b/.github/workflows/website.yml index 7232544..9b7f547 100644 --- a/.github/workflows/website.yml +++ b/.github/workflows/website.yml @@ -26,6 +26,8 @@ jobs: cd demo npm install npm install .. --install-links + rm -rf ../node_modules/java-caller + rm -rf node_modules/java-caller npm run build cd .. cp -R docs demo/dist diff --git a/package.json b/package.json index 9a30e16..5089848 100644 --- a/package.json +++ b/package.json @@ -29,12 +29,14 @@ "test:setup-javascript": "./tests/integration/run-javascript.sh", "test:setup-php": "./tests/integration/run-php.sh", "test:setup-ruby": "./tests/integration/run-ruby.sh", + "test:setup-java": "./tests/integration/run-java.sh", "test:integration": "jest tests/integration", "test:integration-curl": "./scripts/test-format.sh curl", "test:integration-python": "./scripts/test-format.sh python", "test:integration-javascript": "./scripts/test-format.sh javascript", "test:integration-php": "./scripts/test-format.sh php", "test:integration-ruby": "./scripts/test-format.sh ruby", + "test:integration-java": "./scripts/test-format.sh java", "test:example": "./scripts/test-example.sh", "fix": "npm run fix:lint && npm run fix:prettier", "fix:lint": "eslint src tests --fix --ignore-pattern tests/wasm/", @@ -76,6 +78,7 @@ "commander": "^12.1.0", "find-my-way-ts": "^0.1.2", "handlebars": "^4.7.8", + "java-caller": "^4.2.1", "prettier": "^2.8.8" }, "directories": { diff --git a/src/convert.ts b/src/convert.ts index 1480277..e474c86 100644 --- a/src/convert.ts +++ b/src/convert.ts @@ -6,6 +6,7 @@ import { CurlExporter } from "./exporters/curl"; import { JavaScriptExporter } from "./exporters/javascript"; import { PHPExporter } from "./exporters/php"; import { RubyExporter } from "./exporters/ruby"; +import { JavaExporter } from "./exporters/java"; import util from "util"; const isBrowser = typeof window !== "undefined"; @@ -35,6 +36,7 @@ export type ConvertOptions = { * This interface defines the structure of a language exporter. */ export interface FormatExporter { + available?(): boolean; check(requests: ParsedRequest[]): Promise; convert(requests: ParsedRequest[], options: ConvertOptions): Promise; } @@ -44,9 +46,10 @@ const EXPORTERS: Record = { php: new PHPExporter(), python: new PythonExporter(), ruby: new RubyExporter(), + java: new JavaExporter(), curl: new CurlExporter(), }; -const LANGUAGES = ["JavaScript", "PHP", "Python", "Ruby", "curl"]; +const LANGUAGES = ["JavaScript", "PHP", "Python", "Ruby", "Java", "curl"]; /** * Return the list of available export formats. @@ -55,7 +58,16 @@ const LANGUAGES = ["JavaScript", "PHP", "Python", "Ruby", "curl"]; * to use in the `convertRequests()` function. */ export function listFormats(): string[] { - return LANGUAGES; + return LANGUAGES.filter((lang) => { + const exporter = EXPORTERS[lang.toLowerCase()]; + if (!exporter) { + return false; + } + if (!exporter.available) { + return true; + } + return exporter.available(); + }); } /** diff --git a/src/exporters/java.ts b/src/exporters/java.ts new file mode 100644 index 0000000..f762334 --- /dev/null +++ b/src/exporters/java.ts @@ -0,0 +1,84 @@ +import { FormatExporter, ConvertOptions } from "../convert"; +import { ParsedRequest, JSONValue } from "../parse"; +import { Request } from "../metamodel"; +import { JavaCaller } from "java-caller"; + +type JavaRequest = { + api?: string; + params: Record; + query?: Record; + body: JSONValue; +}; + +function getCodeGenParamNames( + params: Record, + request: Request, +) { + for (const [key, value] of Object.entries(params)) { + if (request?.path) { + for (const prop of request.path) { + if (prop.name === key && prop.codegenName !== undefined) { + delete params[key]; + params[prop.codegenName] = value; + } + } + } + } + return params; +} + +export class JavaExporter implements FormatExporter { + available(): boolean { + return !!process.env.JAVA_ES_REQUEST_CONVERTER_JAR; + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + async check(requests: ParsedRequest[]): Promise { + // only return true if all requests are for Elasticsearch + return requests + .map((req) => req.service == "es") + .reduce((prev, curr) => prev && curr, true); + } + + async convert( + requests: ParsedRequest[], + options: ConvertOptions, + ): Promise { + const javaRequests: JavaRequest[] = []; + for (const request of requests) { + if (request.request) { + const correctParams = getCodeGenParamNames( + request.params, + request.request, + ); + const body = request.body ?? {}; + + const javaRequest: JavaRequest = { + api: request.api, + params: correctParams, + query: request.query, + body: body, + }; + javaRequests.push(javaRequest); + } + } + + const args = []; + args.push(JSON.stringify(javaRequests)); + args.push(options.complete ? "true" : "false"); + args.push(options.elasticsearchUrl ?? ""); + + // preparing the java caller class to call the java request converter jar + const java = new JavaCaller({ + minimumJavaVersion: 21, + jar: process.env.JAVA_ES_REQUEST_CONVERTER_JAR, + }); + const { status, stdout, stderr } = await java.run(args); + // error + if (status) { + throw new Error(stderr); + } + // success! + return stdout; + } +} diff --git a/src/java-caller.d.ts b/src/java-caller.d.ts new file mode 100644 index 0000000..70c1c4c --- /dev/null +++ b/src/java-caller.d.ts @@ -0,0 +1 @@ +declare module "java-caller"; diff --git a/tests/integration/convert.test.ts b/tests/integration/convert.test.ts index 78615b1..c86a119 100644 --- a/tests/integration/convert.test.ts +++ b/tests/integration/convert.test.ts @@ -17,6 +17,7 @@ const TEST_FORMATS: Record = { php: "php", curl: "sh", ruby: "rb", + java: "java", }; interface SchemaExample { diff --git a/tests/integration/java-app/pom.xml b/tests/integration/java-app/pom.xml new file mode 100644 index 0000000..d1f5a6c --- /dev/null +++ b/tests/integration/java-app/pom.xml @@ -0,0 +1,109 @@ + + + 4.0.0 + + com.example + app + 1.0-SNAPSHOT + + app + + http://www.example.com + + + UTF-8 + 17 + + + + + + org.junit + junit-bom + 5.11.0 + pom + import + + + + + + + org.junit.jupiter + junit-jupiter-api + test + + + + org.junit.jupiter + junit-jupiter-params + test + + + + co.elastic.clients + elasticsearch-java + 9.2.0 + + + + + + + + + maven-clean-plugin + 3.4.0 + + + + maven-resources-plugin + 3.3.1 + + + maven-compiler-plugin + 3.13.0 + + + maven-surefire-plugin + 3.3.0 + + + maven-jar-plugin + 3.4.2 + + + maven-install-plugin + 3.1.2 + + + maven-deploy-plugin + 3.1.2 + + + + maven-site-plugin + 3.12.1 + + + maven-project-info-reports-plugin + 3.6.1 + + + maven-assembly-plugin + + + + com.example.App + + + + jar-with-dependencies + + + + + + + diff --git a/tests/integration/run-java.sh b/tests/integration/run-java.sh new file mode 100755 index 0000000..04c87d3 --- /dev/null +++ b/tests/integration/run-java.sh @@ -0,0 +1,36 @@ +#!/bin/bash -x +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) +CURRENT_DIR=$(pwd) +BRANCH=$(jq -r .version package.json | grep -Eo "^[0-9]+\.[0-9]+") + +if [[ "$1" == "" ]]; then + # the `test:setup` command runs this script without arguments to initialize + # the environment, so we first delete any previous one + rm -rf $SCRIPT_DIR/.java-es-request-converter + rm -rf $SCRIPT_DIR/.java +fi + +if [[ ! -d $SCRIPT_DIR/.java-es-request-converter ]]; then + git clone git@github.com:l-trotta/java-es-request-converter $SCRIPT_DIR/.java-es-request-converter + cd $SCRIPT_DIR/.java-es-request-converter + ./gradlew jar + cd $CURRENT_DIR +fi + +if [[ ! -d $SCRIPT_DIR/.java ]]; then + mkdir $SCRIPT_DIR/.java + echo "Installing from branch $BRANCH." + git clone -b "$BRANCH" --depth=1 "https://github.com/elastic/elasticsearch-java.git" $SCRIPT_DIR/.java || + (echo "Branch $BRANCH not found. Cloning main branch." && + git clone -b "main" --depth=1 "https://github.com/elastic/elasticsearch-java.git" $SCRIPT_DIR/.java) +fi + +if [[ "$1" != "" ]]; then + cd $SCRIPT_DIR/java-app + mkdir -p src/main/java/com/example + cp $CURRENT_DIR/$1 src/main/java/com/example/App.java + mvn clean compile assembly:single + if [[ "$?" == "0" ]]; then + java -jar target/app-1.0-SNAPSHOT-jar-with-dependencies.jar + fi +fi From 36ae63e26cb8198815b8029734140a79a186eba2 Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Wed, 3 Dec 2025 10:09:20 +0000 Subject: [PATCH 02/15] replacing java-caller package with native JS exec function --- .github/workflows/website.yml | 2 -- package.json | 1 - src/exporters/java.ts | 26 ++++++++++++++------------ 3 files changed, 14 insertions(+), 15 deletions(-) diff --git a/.github/workflows/website.yml b/.github/workflows/website.yml index 9b7f547..7232544 100644 --- a/.github/workflows/website.yml +++ b/.github/workflows/website.yml @@ -26,8 +26,6 @@ jobs: cd demo npm install npm install .. --install-links - rm -rf ../node_modules/java-caller - rm -rf node_modules/java-caller npm run build cd .. cp -R docs demo/dist diff --git a/package.json b/package.json index 5089848..ad01792 100644 --- a/package.json +++ b/package.json @@ -78,7 +78,6 @@ "commander": "^12.1.0", "find-my-way-ts": "^0.1.2", "handlebars": "^4.7.8", - "java-caller": "^4.2.1", "prettier": "^2.8.8" }, "directories": { diff --git a/src/exporters/java.ts b/src/exporters/java.ts index f762334..2202647 100644 --- a/src/exporters/java.ts +++ b/src/exporters/java.ts @@ -1,7 +1,11 @@ +import childProcess from "child_process"; import { FormatExporter, ConvertOptions } from "../convert"; import { ParsedRequest, JSONValue } from "../parse"; import { Request } from "../metamodel"; -import { JavaCaller } from "java-caller"; +import util from "util"; + +const isBrowser = typeof window !== "undefined"; +const execAsync = !isBrowser ? util.promisify(childProcess.exec) : undefined; type JavaRequest = { api?: string; @@ -29,7 +33,7 @@ function getCodeGenParamNames( export class JavaExporter implements FormatExporter { available(): boolean { - return !!process.env.JAVA_ES_REQUEST_CONVERTER_JAR; + return !isBrowser && !!process.env.JAVA_ES_REQUEST_CONVERTER_JAR; } // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -68,17 +72,15 @@ export class JavaExporter implements FormatExporter { args.push(options.complete ? "true" : "false"); args.push(options.elasticsearchUrl ?? ""); - // preparing the java caller class to call the java request converter jar - const java = new JavaCaller({ - minimumJavaVersion: 21, - jar: process.env.JAVA_ES_REQUEST_CONVERTER_JAR, - }); - const { status, stdout, stderr } = await java.run(args); - // error - if (status) { - throw new Error(stderr); + if (execAsync === undefined) { + throw new Error("Cannot use exec()"); + } + const { stdout, stderr } = await execAsync( + `java -jar ${process.env.JAVA_ES_REQUEST_CONVERTER_JAR} '${args[0]}' '${args[1]}' '${args[2]}'`, + ); + if (!stdout) { + throw new Error(`Could not invoke exporter: ${stderr}`); } - // success! return stdout; } } From 09c7c4f2891429982dc21b768ecc58bda5bf9bab Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Wed, 3 Dec 2025 17:26:13 +0000 Subject: [PATCH 03/15] Update .github/workflows/daily.yml Co-authored-by: Laura Trotta <153528055+l-trotta@users.noreply.github.com> --- .github/workflows/integration.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index 5a70ec4..166d4a6 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -135,4 +135,4 @@ jobs: npm run test:setup-java - name: Integration tests run: | - JAVA_ES_REQUEST_CONVERTER_JAR=/home/runner/work/request-converter/request-converter/tests/integration/.java-es-request-converter/build/libs/java-es-request-converter-1.0-SNAPSHOT.jar npm run test:integration-java + JAVA_ES_REQUEST_CONVERTER_JAR=/home/runner/work/request-converter/request-converter/tests/integration/.java-request-converter/build/libs/java-request-converter-1.0.0.jar npm run test:integration-java From d124a3d2fd55429ddbcbca23f5b9edb692bef57c Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Wed, 3 Dec 2025 18:13:07 +0000 Subject: [PATCH 04/15] Update tests/integration/run-java.sh Co-authored-by: Laura Trotta <153528055+l-trotta@users.noreply.github.com> --- tests/integration/run-java.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/run-java.sh b/tests/integration/run-java.sh index 04c87d3..dd6b94b 100755 --- a/tests/integration/run-java.sh +++ b/tests/integration/run-java.sh @@ -11,7 +11,7 @@ if [[ "$1" == "" ]]; then fi if [[ ! -d $SCRIPT_DIR/.java-es-request-converter ]]; then - git clone git@github.com:l-trotta/java-es-request-converter $SCRIPT_DIR/.java-es-request-converter + git clone git@github.com:elastic/java-request-converter $SCRIPT_DIR/.java-request-converter cd $SCRIPT_DIR/.java-es-request-converter ./gradlew jar cd $CURRENT_DIR From 59b3e286815e802e07b5bf971118aab0ef22ee58 Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Wed, 3 Dec 2025 18:13:37 +0000 Subject: [PATCH 05/15] Update tests/integration/run-java.sh Co-authored-by: Laura Trotta <153528055+l-trotta@users.noreply.github.com> --- tests/integration/run-java.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/run-java.sh b/tests/integration/run-java.sh index dd6b94b..d03dcdd 100755 --- a/tests/integration/run-java.sh +++ b/tests/integration/run-java.sh @@ -6,7 +6,7 @@ BRANCH=$(jq -r .version package.json | grep -Eo "^[0-9]+\.[0-9]+") if [[ "$1" == "" ]]; then # the `test:setup` command runs this script without arguments to initialize # the environment, so we first delete any previous one - rm -rf $SCRIPT_DIR/.java-es-request-converter + rm -rf $SCRIPT_DIR/.java-request-converter rm -rf $SCRIPT_DIR/.java fi From 381ed77d53fe08580d114ff21c769e20f37c3881 Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Wed, 3 Dec 2025 18:13:50 +0000 Subject: [PATCH 06/15] Update tests/integration/run-java.sh Co-authored-by: Laura Trotta <153528055+l-trotta@users.noreply.github.com> --- tests/integration/run-java.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/run-java.sh b/tests/integration/run-java.sh index d03dcdd..17905e2 100755 --- a/tests/integration/run-java.sh +++ b/tests/integration/run-java.sh @@ -12,7 +12,7 @@ fi if [[ ! -d $SCRIPT_DIR/.java-es-request-converter ]]; then git clone git@github.com:elastic/java-request-converter $SCRIPT_DIR/.java-request-converter - cd $SCRIPT_DIR/.java-es-request-converter + cd $SCRIPT_DIR/.java-request-converter ./gradlew jar cd $CURRENT_DIR fi From fc1fcfeb09219b3976696f14e21641f3f7bc5acb Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Wed, 3 Dec 2025 18:16:51 +0000 Subject: [PATCH 07/15] Add temporary integration tests for Java --- .github/workflows/ci.yml | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9c2d15d..a5f4c0f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -71,3 +71,28 @@ jobs: - name: Tests run: | npm run test + # only added temporarily to run integration tests on Java + integration-tests-java: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Setup node.js + uses: actions/setup-node@v4 + - name: Install + run: | + npm install + - name: Build + run: | + npm run build + - name: Install Java + uses: actions/setup-java@v5 + with: + distribution: temurin + java-version: 21 + - name: Prepare for testing + run: | + npm run + npm run test:setup-java + - name: Integration tests + run: | + JAVA_ES_REQUEST_CONVERTER_JAR=/home/runner/work/request-converter/request-converter/tests/integration/.java-request-converter/build/libs/java-request-converter-1.0.0.jar npm run test:integration-java From 160ac7ecbabcfb304293f6c2f8066275dcd155c0 Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Wed, 3 Dec 2025 18:23:41 +0000 Subject: [PATCH 08/15] use public repository url --- tests/integration/run-java.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/run-java.sh b/tests/integration/run-java.sh index 17905e2..1f89ba0 100755 --- a/tests/integration/run-java.sh +++ b/tests/integration/run-java.sh @@ -11,7 +11,7 @@ if [[ "$1" == "" ]]; then fi if [[ ! -d $SCRIPT_DIR/.java-es-request-converter ]]; then - git clone git@github.com:elastic/java-request-converter $SCRIPT_DIR/.java-request-converter + git clone https://github.com/elastic/java-request-converter $SCRIPT_DIR/.java-request-converter cd $SCRIPT_DIR/.java-request-converter ./gradlew jar cd $CURRENT_DIR From 9c748ca4e4ea7a4847a722658c77bc9076838efc Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Wed, 3 Dec 2025 19:56:16 +0000 Subject: [PATCH 09/15] com.example.App -> org.example.App --- src/exporters/java.ts | 12 +++++++----- tests/integration/java-app/pom.xml | 4 ++-- tests/integration/run-java.sh | 4 ++-- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/exporters/java.ts b/src/exporters/java.ts index 2202647..2886bd9 100644 --- a/src/exporters/java.ts +++ b/src/exporters/java.ts @@ -67,16 +67,18 @@ export class JavaExporter implements FormatExporter { } } - const args = []; - args.push(JSON.stringify(javaRequests)); - args.push(options.complete ? "true" : "false"); - args.push(options.elasticsearchUrl ?? ""); + // escape single quotes that may appear in the payload + // (only for bash-style shells for now) + const req = JSON.stringify(javaRequests).replaceAll("'", "'\"'\"'"); + // other arguments to the Java converter + const complete = options.complete ? "true" : "false"; + const url = options.elasticsearchUrl ?? ""; if (execAsync === undefined) { throw new Error("Cannot use exec()"); } const { stdout, stderr } = await execAsync( - `java -jar ${process.env.JAVA_ES_REQUEST_CONVERTER_JAR} '${args[0]}' '${args[1]}' '${args[2]}'`, + `java -jar ${process.env.JAVA_ES_REQUEST_CONVERTER_JAR} '${req}' '${complete}' '${url}'`, ); if (!stdout) { throw new Error(`Could not invoke exporter: ${stderr}`); diff --git a/tests/integration/java-app/pom.xml b/tests/integration/java-app/pom.xml index d1f5a6c..5d236e5 100644 --- a/tests/integration/java-app/pom.xml +++ b/tests/integration/java-app/pom.xml @@ -3,7 +3,7 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - com.example + org.example app 1.0-SNAPSHOT @@ -95,7 +95,7 @@ - com.example.App + org.example.App diff --git a/tests/integration/run-java.sh b/tests/integration/run-java.sh index 1f89ba0..fb42191 100755 --- a/tests/integration/run-java.sh +++ b/tests/integration/run-java.sh @@ -27,8 +27,8 @@ fi if [[ "$1" != "" ]]; then cd $SCRIPT_DIR/java-app - mkdir -p src/main/java/com/example - cp $CURRENT_DIR/$1 src/main/java/com/example/App.java + mkdir -p src/main/java/org/example + cp $CURRENT_DIR/$1 src/main/java/org/example/App.java mvn clean compile assembly:single if [[ "$?" == "0" ]]; then java -jar target/app-1.0-SNAPSHOT-jar-with-dependencies.jar From 950c1ee1ac86f8ca17ac766b393cc39f14ccf542 Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Wed, 3 Dec 2025 22:24:40 +0000 Subject: [PATCH 10/15] ignore errors in response parsing --- tests/integration/run-java.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/integration/run-java.sh b/tests/integration/run-java.sh index fb42191..e18f510 100755 --- a/tests/integration/run-java.sh +++ b/tests/integration/run-java.sh @@ -1,4 +1,4 @@ -#!/bin/bash -x +#!/bin/bash SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) CURRENT_DIR=$(pwd) BRANCH=$(jq -r .version package.json | grep -Eo "^[0-9]+\.[0-9]+") @@ -31,6 +31,6 @@ if [[ "$1" != "" ]]; then cp $CURRENT_DIR/$1 src/main/java/org/example/App.java mvn clean compile assembly:single if [[ "$?" == "0" ]]; then - java -jar target/app-1.0-SNAPSHOT-jar-with-dependencies.jar + java -jar target/app-1.0-SNAPSHOT-jar-with-dependencies.jar || true fi fi From d77f24227b8dfb6f873aade41cbf78c3877d2a98 Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Mon, 15 Dec 2025 14:20:11 +0000 Subject: [PATCH 11/15] do not clean the build between tests --- tests/integration/run-java.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/run-java.sh b/tests/integration/run-java.sh index e18f510..35b9f58 100755 --- a/tests/integration/run-java.sh +++ b/tests/integration/run-java.sh @@ -29,7 +29,7 @@ if [[ "$1" != "" ]]; then cd $SCRIPT_DIR/java-app mkdir -p src/main/java/org/example cp $CURRENT_DIR/$1 src/main/java/org/example/App.java - mvn clean compile assembly:single + mvn compile assembly:single if [[ "$?" == "0" ]]; then java -jar target/app-1.0-SNAPSHOT-jar-with-dependencies.jar || true fi From ec80500af56166655c494662c3d284e378318a86 Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Tue, 23 Dec 2025 19:04:45 +0000 Subject: [PATCH 12/15] addressed some of the Java errors --- tests/integration/convert.test.ts | 87 ++++++++++++++++++++++++++++--- tests/integration/skip.ts | 78 +++++++++++++++++++++++++++ 2 files changed, 158 insertions(+), 7 deletions(-) diff --git a/tests/integration/convert.test.ts b/tests/integration/convert.test.ts index c86a119..63179d1 100644 --- a/tests/integration/convert.test.ts +++ b/tests/integration/convert.test.ts @@ -20,6 +20,14 @@ const TEST_FORMATS: Record = { java: "java", }; +// For the languages listed, bodies are compared taking into account that some +// array or object properties may have been given in their shortcut form, but +// the language client expands them to full form. +// For these languages, a property that is expected to have a scalar value but +// instead comes back as a single-key dictionary or single-element array is +// compared against the value wrapped in the object or array. +const checkExpandedShortcuts = ["java"]; + interface SchemaExample { method_request: string; value: string; @@ -30,6 +38,55 @@ interface Example { source: string; } +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function deepCompare(actual: any, expected: any): boolean { + try { + // first try a standard comparison + expect(actual).toEqual(expected); + } catch (error) { + return false; + } + return true; +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function compareWithShortcuts(actual: any, expected: any): boolean { + if (deepCompare(actual, expected)) { + return true; + } + + // check for single-element arrays + if (Array.isArray(actual) && actual.length === 1) { + if (deepCompare(actual[0], expected)) { + return true; + } + } + + // check for single-key objects + if (typeof actual === "object" && Object.keys(actual).length === 1) { + if (deepCompare(actual[Object.keys(actual)[0]], expected)) { + return true; + } + } + + if (typeof actual !== "object" || typeof expected !== "object") { + return false; // the caller will do a complete assert and report the diff + } + + // for objects we recursively compare its properties + for (const actualProp in actual) { + if (!compareWithShortcuts(actual[actualProp], expected[actualProp])) { + return false; + } + } + for (const expectedProp in expected) { + if (!Object.keys(actual).includes(expectedProp)) { + return false; + } + } + return true; +} + beforeAll(async () => { // start a simple web server that will capture requests sent when running // converted scripts @@ -163,13 +220,29 @@ describe("convert", () => { result: parsedRequest?.query ?? {}, source, }); - expect( - { result: capturedRequest.body, source }, - failureMessage, - ).toEqual({ - result: parsedRequest?.body ?? {}, - source, - }); + if (checkExpandedShortcuts.includes(format)) { + if ( + !compareWithShortcuts(capturedRequest.body, parsedRequest?.body) + ) { + // A comparison accounting for shortcut properties came as different + // so now we do a full assert to report this error + expect( + { result: capturedRequest.body, source }, + failureMessage, + ).toEqual({ + result: parsedRequest?.body ?? {}, + source, + }); + } + } else { + expect( + { result: capturedRequest.body, source }, + failureMessage, + ).toEqual({ + result: parsedRequest?.body ?? {}, + source, + }); + } }, ); } diff --git a/tests/integration/skip.ts b/tests/integration/skip.ts index 04fc379..83051b7 100644 --- a/tests/integration/skip.ts +++ b/tests/integration/skip.ts @@ -112,6 +112,84 @@ const skip: Record = { reason: "removed endpoint", formats: ["ruby", "javascript"], }, + + // the following examples trigger known bugs in the Java request converter or + // the Elasticsearch specification, so for now they are skipped + PutScriptRequestExample1: { + reason: "spec bug", + formats: ["java"], + }, + indicesPutSettingsRequestExample2: { + reason: "spec bug", + formats: ["java"], + }, + IndicesPutDataLifecycleRequestExample2: { + reason: "spec bug", + formats: ["java"], + }, + IndicesCreateFromExample1: { + reason: "java client bug", + formats: ["java"], + }, + indicesSimulateIndexTemplateRequestExample1: { + reason: "java client bug", + formats: ["java"], + }, + + // the following examples are not currently supported by the Java request converter + // so they are skipped + BulkRequestExample1: { + reason: "unsupported", + formats: ["java"], + }, + BulkRequestExample2: { + reason: "unsupported", + formats: ["java"], + }, + BulkRequestExample3: { + reason: "unsupported", + formats: ["java"], + }, + BulkRequestExample4: { + reason: "unsupported", + formats: ["java"], + }, + MultiGetRequestExample2: { + reason: "unsupported", + formats: ["java"], + }, + MsearchRequestExample1: { + reason: "unsupported", + formats: ["java"], + }, + MultiSearchTemplateRequestExample1: { + reason: "unsupported", + formats: ["java"], + }, + ConnectorUpdateLastSyncRequestExample1: { + reason: "unsupported", + formats: ["java"], + }, + AsyncQueryRequestExample1: { + reason: "unsupported", + formats: ["java"], + }, + EsqlAsyncQueryDeleteExample1: { + reason: "unsupported", + formats: ["java"], + }, + EsqlAsyncQueryGetExample1: { + reason: "unsupported", + formats: ["java"], + }, + EsqlAsyncQueryStopExample1: { + reason: "unsupported", + formats: ["java"], + }, + FindStructureRequestExample1: { + reason: "unsupported", + formats: ["java"], + }, }; export function shouldBeSkipped( From e85cfb4bb34a0117e2a30d011fbe327ecae64466 Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Wed, 24 Dec 2025 13:13:30 +0000 Subject: [PATCH 13/15] address string vs int/boolean type differences in Java --- package.json | 4 +-- tests/integration/convert.test.ts | 59 +++++++++++++++++++++++++++---- 2 files changed, 54 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index ad01792..bb282f3 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "compile-templates": "node scripts/compile-templates.mjs", "copy-files": "copyfiles -u 1 src/**/*.js src/**/*.json dist/", "docs": "typedoc src/index.ts", - "lint": "eslint src tests --ignore-pattern tests/wasm/", + "lint": "eslint src tests --ignore-pattern tests/wasm/ --ignore-pattern 'tests/integration/.*'", "prettier": "prettier \"src/**/*.ts\" \"tests/**/*.ts\" --list-different", "test": "npm run compile-templates && jest --test-path-ignore-patterns integration --coverage", "test:setup": "./tests/integration/run-python.sh && ./tests/integration/run-javascript.sh && ./tests/integration/run-ruby.sh && ./tests/integration/run-php.sh", @@ -39,7 +39,7 @@ "test:integration-java": "./scripts/test-format.sh java", "test:example": "./scripts/test-example.sh", "fix": "npm run fix:lint && npm run fix:prettier", - "fix:lint": "eslint src tests --fix --ignore-pattern tests/wasm/", + "fix:lint": "eslint src tests --fix --ignore-pattern tests/wasm/ --ignore-pattern 'tests/integration/.*'", "fix:prettier": "prettier \"src/**/*.ts\" \"tests/**/*.ts\" --write", "fix:precommit": "./scripts/precommit.sh", "watch:test": "npm run compile-templates && jest --test-path-ignore-patterns integration --coverage --watch", diff --git a/tests/integration/convert.test.ts b/tests/integration/convert.test.ts index 63179d1..1140473 100644 --- a/tests/integration/convert.test.ts +++ b/tests/integration/convert.test.ts @@ -69,6 +69,35 @@ function compareWithShortcuts(actual: any, expected: any): boolean { } } + // check for integer vs string + if (typeof actual === "number" && typeof expected === "string") { + return actual === parseFloat(expected); + } + + if (typeof actual === "string" && typeof expected === "number") { + return parseFloat(actual) === expected; + } + + // check for boolean vs string + if (typeof actual === "boolean" && typeof expected === "string") { + return actual ? expected === "true" : expected === "false"; + } + if (typeof actual === "string" && typeof expected === "boolean") { + return expected ? actual === "true" : actual === "false"; + } + + // check floats given as strings + const NUMERIC_REGEX = /^[-+]?(\d+\.\d+|\d+|\.\d+)$/; + if ( + typeof actual === "string" && + typeof expected === "string" && + NUMERIC_REGEX.test(actual) && + NUMERIC_REGEX.test(expected) + ) { + console.log("heyyy"); + return parseFloat(actual) === parseFloat(expected); + } + if (typeof actual !== "object" || typeof expected !== "object") { return false; // the caller will do a complete assert and report the diff } @@ -213,13 +242,29 @@ describe("convert", () => { result: parsedRequest?.path, source, }); - expect( - { result: capturedRequest.query, source }, - failureMessage, - ).toEqual({ - result: parsedRequest?.query ?? {}, - source, - }); + if (checkExpandedShortcuts.includes(format)) { + if ( + !compareWithShortcuts(capturedRequest.query, parsedRequest?.query) + ) { + // A comparison accounting for shortcut properties came as different + // so now we do a full assert to report this error + expect( + { result: capturedRequest.query, source }, + failureMessage, + ).toEqual({ + result: parsedRequest?.query ?? {}, + source, + }); + } + } else { + expect( + { result: capturedRequest.query, source }, + failureMessage, + ).toEqual({ + result: parsedRequest?.query ?? {}, + source, + }); + } if (checkExpandedShortcuts.includes(format)) { if ( !compareWithShortcuts(capturedRequest.body, parsedRequest?.body) From 13aed3be8279be0317d87532e4c68dc6778ab229 Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Tue, 6 Jan 2026 14:59:49 +0000 Subject: [PATCH 14/15] add more flexible comparisons for Java tests --- tests/integration/convert.test.ts | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/tests/integration/convert.test.ts b/tests/integration/convert.test.ts index 1140473..515205a 100644 --- a/tests/integration/convert.test.ts +++ b/tests/integration/convert.test.ts @@ -94,10 +94,20 @@ function compareWithShortcuts(actual: any, expected: any): boolean { NUMERIC_REGEX.test(actual) && NUMERIC_REGEX.test(expected) ) { - console.log("heyyy"); return parseFloat(actual) === parseFloat(expected); } + // check for strings that have `\n` in them + if (typeof actual === "string" && typeof expected === "string") { + if (actual === expected) { + return true; + } + if (actual.includes("\n") && actual.replaceAll("\n", "\\n") === expected) { + return true; + } + return false; + } + if (typeof actual !== "object" || typeof expected !== "object") { return false; // the caller will do a complete assert and report the diff } @@ -223,7 +233,9 @@ describe("convert", () => { for (const param in parsedRequest?.query ?? {}) { if ( - capturedRequest.query[param] === undefined && + (capturedRequest.query === undefined || + capturedRequest.query[param] === undefined) && + capturedRequest.body && capturedRequest.body[param] !== undefined ) { // the client moved a query argument to the body @@ -289,6 +301,7 @@ describe("convert", () => { }); } }, + 30000, // timeout ); } } From 84599a355e0f17c7b61f1c96556b6b9f85b005f9 Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Fri, 15 May 2026 16:39:40 +0100 Subject: [PATCH 15/15] report java compilation failures in CI --- tests/integration/run-java.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/integration/run-java.sh b/tests/integration/run-java.sh index 35b9f58..217fec8 100755 --- a/tests/integration/run-java.sh +++ b/tests/integration/run-java.sh @@ -32,5 +32,7 @@ if [[ "$1" != "" ]]; then mvn compile assembly:single if [[ "$?" == "0" ]]; then java -jar target/app-1.0-SNAPSHOT-jar-with-dependencies.jar || true + else + exit 1 fi fi