From 9ba9fae0698b254d74a8e6fae12ee54473fa2da0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Mart=C3=ADnez=20Rinc=C3=B3n?= Date: Sun, 2 Nov 2025 22:16:55 +0100 Subject: [PATCH 1/7] feat: implement Sprint RestClient target --- TARGETS.md | 10 +++ src/targets/java/index.js | 3 +- src/targets/java/restclient.js | 81 +++++++++++++++++++ test/fixtures/available-targets.json | 6 ++ .../restclient/application-form-encoded.java | 10 +++ .../java/restclient/application-json.java | 10 +++ .../output/java/restclient/compression.java | 8 ++ .../output/java/restclient/cookies.java | 9 +++ .../output/java/restclient/custom-method.java | 7 ++ .../fixtures/output/java/restclient/full.java | 13 +++ .../output/java/restclient/headers.java | 10 +++ .../output/java/restclient/https.java | 7 ++ .../java/restclient/jsonObj-multiline.java | 10 +++ .../java/restclient/jsonObj-null-value.java | 10 +++ .../output/java/restclient/malicious.java | 25 ++++++ .../java/restclient/multipart-data.java | 10 +++ .../java/restclient/multipart-file.java | 10 +++ .../java/restclient/multipart-form-data.java | 10 +++ .../output/java/restclient/nested.java | 7 ++ .../output/java/restclient/query.java | 7 ++ .../output/java/restclient/short.java | 7 ++ .../output/java/restclient/text-plain.java | 10 +++ .../java/restclient/unparseable-query.java | 7 ++ test/targets/java/restclient.js | 5 ++ 24 files changed, 291 insertions(+), 1 deletion(-) create mode 100644 src/targets/java/restclient.js create mode 100644 test/fixtures/output/java/restclient/application-form-encoded.java create mode 100644 test/fixtures/output/java/restclient/application-json.java create mode 100644 test/fixtures/output/java/restclient/compression.java create mode 100644 test/fixtures/output/java/restclient/cookies.java create mode 100644 test/fixtures/output/java/restclient/custom-method.java create mode 100644 test/fixtures/output/java/restclient/full.java create mode 100644 test/fixtures/output/java/restclient/headers.java create mode 100644 test/fixtures/output/java/restclient/https.java create mode 100644 test/fixtures/output/java/restclient/jsonObj-multiline.java create mode 100644 test/fixtures/output/java/restclient/jsonObj-null-value.java create mode 100644 test/fixtures/output/java/restclient/malicious.java create mode 100644 test/fixtures/output/java/restclient/multipart-data.java create mode 100644 test/fixtures/output/java/restclient/multipart-file.java create mode 100644 test/fixtures/output/java/restclient/multipart-form-data.java create mode 100644 test/fixtures/output/java/restclient/nested.java create mode 100644 test/fixtures/output/java/restclient/query.java create mode 100644 test/fixtures/output/java/restclient/short.java create mode 100644 test/fixtures/output/java/restclient/text-plain.java create mode 100644 test/fixtures/output/java/restclient/unparseable-query.java create mode 100644 test/targets/java/restclient.js diff --git a/TARGETS.md b/TARGETS.md index 08d9986..786fcc4 100644 --- a/TARGETS.md +++ b/TARGETS.md @@ -40,6 +40,16 @@ Currently the following output targets are supported: | --------- | ------- | -------------------------------- | | `indent` | ` ` | line break & indent output value | +### [RestClient](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/client/RestClient.html) + +> Spring Framework REST client + +###### Options + +| Option | Default | Description | +| --------- | ------- | -------------------------------- | +| `indent` | ` ` | line break & indent output value | + ---- ## JavaScript diff --git a/src/targets/java/index.js b/src/targets/java/index.js index 37b0db7..ca0b259 100644 --- a/src/targets/java/index.js +++ b/src/targets/java/index.js @@ -11,5 +11,6 @@ module.exports = { okhttp: require('./okhttp'), unirest: require('./unirest'), asynchttp: require('./asynchttp'), - nethttp: require('./nethttp') + nethttp: require('./nethttp'), + restclient: require('./restclient') } diff --git a/src/targets/java/restclient.js b/src/targets/java/restclient.js new file mode 100644 index 0000000..dd98097 --- /dev/null +++ b/src/targets/java/restclient.js @@ -0,0 +1,81 @@ +/** + * @description + * HTTP code snippet generator for Java using Spring RestClient. + * + * @author + * @jamezrin + * + * for any questions or issues regarding the generated code snippet, please open an issue mentioning the author. + */ + +'use strict' + +const CodeBuilder = require('../../helpers/code-builder') + +const standardMethods = ['GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS', 'TRACE'] + +module.exports = function (source, options) { + const opts = Object.assign({ + indent: ' ' + }, options) + + const code = new CodeBuilder(opts.indent) + + code.push('RestClient restClient = RestClient.create();') + .blank() + + code.push('ResponseEntity response = restClient') + + if (standardMethods.includes(source.method.toUpperCase())) { + code.push(1, '.method(HttpMethod.%s)', source.method.toUpperCase()) + } else { + code.push(1, '.method(HttpMethod.valueOf("%s"))', source.method.toUpperCase()) + } + + code.push(1, '.uri("%s")', source.fullUrl) + + if (source.cookies && source.cookies.length) { + source.cookies.forEach(function (cookie) { + code.push(1, '.cookie("%s", "%s")', cookie.name, cookie.value) + }) + } + + const headers = Object.keys(source.allHeaders).filter(function (key) { + return key.toLowerCase() !== 'cookie' + }) + if (headers.length) { + headers.forEach(function (key) { + code.push(1, '.header("%s", "%qd")', key, source.allHeaders[key]) + }) + } + + if (source.postData && source.postData.text) { + if (source.postData.mimeType === 'application/json') { + code.push(1, '.contentType(MediaType.APPLICATION_JSON)') + code.push(1, '.body(%s)', JSON.stringify(source.postData.text)) + } else if (source.postData.mimeType === 'application/x-www-form-urlencoded') { + code.push(1, '.contentType(MediaType.APPLICATION_FORM_URLENCODED)') + code.push(1, '.body(%s)', JSON.stringify(source.postData.text)) + } else if (source.postData.mimeType && source.postData.mimeType.startsWith('multipart/form-data')) { + code.push(1, '.contentType(MediaType.parseMediaType("multipart/form-data"))') + code.push(1, '.body(%s)', JSON.stringify(source.postData.text)) + } else { + if (source.postData.mimeType) { + code.push(1, '.contentType(MediaType.parseMediaType("%s"))', source.postData.mimeType) + } + code.push(1, '.body(%s)', JSON.stringify(source.postData.text)) + } + } + + code.push(1, '.retrieve()') + code.push(1, '.toEntity(String.class);') + + return code.join() +} + +module.exports.info = { + key: 'restclient', + title: 'Spring RestClient', + link: 'https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/client/RestClient.html', + description: 'Spring Framework REST client' +} diff --git a/test/fixtures/available-targets.json b/test/fixtures/available-targets.json index 7ad7857..fa36970 100644 --- a/test/fixtures/available-targets.json +++ b/test/fixtures/available-targets.json @@ -226,6 +226,12 @@ "title": "java.net.http", "link": "https://openjdk.java.net/groups/net/httpclient/intro.html", "description": "Java Standardized HTTP Client API" + }, + { + "key": "restclient", + "title": "Spring RestClient", + "link": "https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/client/RestClient.html", + "description": "Spring Framework REST client" } ] }, diff --git a/test/fixtures/output/java/restclient/application-form-encoded.java b/test/fixtures/output/java/restclient/application-form-encoded.java new file mode 100644 index 0000000..a3b49de --- /dev/null +++ b/test/fixtures/output/java/restclient/application-form-encoded.java @@ -0,0 +1,10 @@ +RestClient restClient = RestClient.create(); + +ResponseEntity response = restClient + .method(HttpMethod.POST) + .uri("http://mockbin.com/har") + .header("content-type", "application/x-www-form-urlencoded") + .contentType(MediaType.APPLICATION_FORM_URLENCODED) + .body("foo=bar&hello=world") + .retrieve() + .toEntity(String.class); \ No newline at end of file diff --git a/test/fixtures/output/java/restclient/application-json.java b/test/fixtures/output/java/restclient/application-json.java new file mode 100644 index 0000000..ebd7a8e --- /dev/null +++ b/test/fixtures/output/java/restclient/application-json.java @@ -0,0 +1,10 @@ +RestClient restClient = RestClient.create(); + +ResponseEntity response = restClient + .method(HttpMethod.POST) + .uri("http://mockbin.com/har") + .header("content-type", "application/json") + .contentType(MediaType.APPLICATION_JSON) + .body("{\"number\":1,\"string\":\"f\\\"oo\",\"arr\":[1,2,3],\"nested\":{\"a\":\"b\"},\"arr_mix\":[1,\"a\",{\"arr_mix_nested\":{}}],\"boolean\":false}") + .retrieve() + .toEntity(String.class); \ No newline at end of file diff --git a/test/fixtures/output/java/restclient/compression.java b/test/fixtures/output/java/restclient/compression.java new file mode 100644 index 0000000..056a80b --- /dev/null +++ b/test/fixtures/output/java/restclient/compression.java @@ -0,0 +1,8 @@ +RestClient restClient = RestClient.create(); + +ResponseEntity response = restClient + .method(HttpMethod.GET) + .uri("http://mockbin.com/har") + .header("accept-encoding", "deflate, gzip, br") + .retrieve() + .toEntity(String.class); \ No newline at end of file diff --git a/test/fixtures/output/java/restclient/cookies.java b/test/fixtures/output/java/restclient/cookies.java new file mode 100644 index 0000000..624d1fd --- /dev/null +++ b/test/fixtures/output/java/restclient/cookies.java @@ -0,0 +1,9 @@ +RestClient restClient = RestClient.create(); + +ResponseEntity response = restClient + .method(HttpMethod.POST) + .uri("http://mockbin.com/har") + .cookie("foo", "bar") + .cookie("bar", "baz") + .retrieve() + .toEntity(String.class); diff --git a/test/fixtures/output/java/restclient/custom-method.java b/test/fixtures/output/java/restclient/custom-method.java new file mode 100644 index 0000000..70eafc5 --- /dev/null +++ b/test/fixtures/output/java/restclient/custom-method.java @@ -0,0 +1,7 @@ +RestClient restClient = RestClient.create(); + +ResponseEntity response = restClient + .method(HttpMethod.valueOf("PROPFIND")) + .uri("http://mockbin.com/har") + .retrieve() + .toEntity(String.class); diff --git a/test/fixtures/output/java/restclient/full.java b/test/fixtures/output/java/restclient/full.java new file mode 100644 index 0000000..83327f0 --- /dev/null +++ b/test/fixtures/output/java/restclient/full.java @@ -0,0 +1,13 @@ +RestClient restClient = RestClient.create(); + +ResponseEntity response = restClient + .method(HttpMethod.POST) + .uri("http://mockbin.com/har?foo=bar&foo=baz&baz=abc&key=value") + .cookie("foo", "bar") + .cookie("bar", "baz") + .header("accept", "application/json") + .header("content-type", "application/x-www-form-urlencoded") + .contentType(MediaType.APPLICATION_FORM_URLENCODED) + .body("foo=bar") + .retrieve() + .toEntity(String.class); diff --git a/test/fixtures/output/java/restclient/headers.java b/test/fixtures/output/java/restclient/headers.java new file mode 100644 index 0000000..229d844 --- /dev/null +++ b/test/fixtures/output/java/restclient/headers.java @@ -0,0 +1,10 @@ +RestClient restClient = RestClient.create(); + +ResponseEntity response = restClient + .method(HttpMethod.GET) + .uri("http://mockbin.com/har") + .header("accept", "application/json") + .header("x-foo", "Bar") + .header("quoted-value", "\"quoted\" 'string'") + .retrieve() + .toEntity(String.class); \ No newline at end of file diff --git a/test/fixtures/output/java/restclient/https.java b/test/fixtures/output/java/restclient/https.java new file mode 100644 index 0000000..0946263 --- /dev/null +++ b/test/fixtures/output/java/restclient/https.java @@ -0,0 +1,7 @@ +RestClient restClient = RestClient.create(); + +ResponseEntity response = restClient + .method(HttpMethod.GET) + .uri("https://mockbin.com/har") + .retrieve() + .toEntity(String.class); \ No newline at end of file diff --git a/test/fixtures/output/java/restclient/jsonObj-multiline.java b/test/fixtures/output/java/restclient/jsonObj-multiline.java new file mode 100644 index 0000000..ae0c92b --- /dev/null +++ b/test/fixtures/output/java/restclient/jsonObj-multiline.java @@ -0,0 +1,10 @@ +RestClient restClient = RestClient.create(); + +ResponseEntity response = restClient + .method(HttpMethod.POST) + .uri("http://mockbin.com/har") + .header("content-type", "application/json") + .contentType(MediaType.APPLICATION_JSON) + .body("{\n \"foo\": \"bar\"\n}") + .retrieve() + .toEntity(String.class); \ No newline at end of file diff --git a/test/fixtures/output/java/restclient/jsonObj-null-value.java b/test/fixtures/output/java/restclient/jsonObj-null-value.java new file mode 100644 index 0000000..f1b6a21 --- /dev/null +++ b/test/fixtures/output/java/restclient/jsonObj-null-value.java @@ -0,0 +1,10 @@ +RestClient restClient = RestClient.create(); + +ResponseEntity response = restClient + .method(HttpMethod.POST) + .uri("http://mockbin.com/har") + .header("content-type", "application/json") + .contentType(MediaType.APPLICATION_JSON) + .body("{\"foo\":null}") + .retrieve() + .toEntity(String.class); \ No newline at end of file diff --git a/test/fixtures/output/java/restclient/malicious.java b/test/fixtures/output/java/restclient/malicious.java new file mode 100644 index 0000000..535a860 --- /dev/null +++ b/test/fixtures/output/java/restclient/malicious.java @@ -0,0 +1,25 @@ +RestClient restClient = RestClient.create(); + +ResponseEntity response = restClient + .method(HttpMethod.POST) + .uri("http://example.test/%27%22%60$(%(%%7B%7B%7B/0%s//?'=squote-key-test&squote-value-test='&%22=dquote-key-test&dquote-value-test=%22&%60=backtick-key-test&backtick-value-test=%60&%24(=dollar-parenthesis-key-test&dollar-parenthesis-value-test=%24(&%23%7B=hash-brace-key-test&hash-brace-value-test=%23%7B&%25(=percent-parenthesis-key-test&percent-parenthesis-value-test=%25(&%25%7B=percent-brace-key-test&percent-brace-value-test=%25%7B&%7B%7B=double-brace-key-test&double-brace-value-test=%7B%7B&%5C0=null-key-test&null-value-test=%5C0&%25s=string-fmt-key-test&string-fmt-value-test=%25s&%5C=slash-key-test&slash-value-test=%5C") + .header("'", "squote-key-test") + .header("squote-value-test", "'") + .header("dquote-value-test", "\"") + .header("`", "backtick-key-test") + .header("backtick-value-test", "`") + .header("$", "dollar-key-test") + .header("dollar-parenthesis-value-test", "$(") + .header("#", "hash-key-test") + .header("hash-brace-value-test", "#{") + .header("%", "percent-key-test") + .header("percent-parenthesis-value-test", "%(") + .header("percent-brace-value-test", "%{") + .header("double-brace-value-test", "{{") + .header("null-value-test", "\\0") + .header("string-fmt-value-test", "%s") + .header("slash-value-test", "\\") + .contentType(MediaType.parseMediaType("text/plain")) + .body("' \" ` $( #{ %( %{ {{ \\0 %s \\") + .retrieve() + .toEntity(String.class); \ No newline at end of file diff --git a/test/fixtures/output/java/restclient/multipart-data.java b/test/fixtures/output/java/restclient/multipart-data.java new file mode 100644 index 0000000..842ac6e --- /dev/null +++ b/test/fixtures/output/java/restclient/multipart-data.java @@ -0,0 +1,10 @@ +RestClient restClient = RestClient.create(); + +ResponseEntity response = restClient + .method(HttpMethod.POST) + .uri("http://mockbin.com/har") + .header("content-type", "multipart/form-data; boundary=---011000010111000001101001") + .contentType(MediaType.parseMediaType("multipart/form-data")) + .body("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"foo\"; filename=\"hello.txt\"\r\nContent-Type: text/plain\r\n\r\nHello World\r\n-----011000010111000001101001--\r\n") + .retrieve() + .toEntity(String.class); \ No newline at end of file diff --git a/test/fixtures/output/java/restclient/multipart-file.java b/test/fixtures/output/java/restclient/multipart-file.java new file mode 100644 index 0000000..bc60ffe --- /dev/null +++ b/test/fixtures/output/java/restclient/multipart-file.java @@ -0,0 +1,10 @@ +RestClient restClient = RestClient.create(); + +ResponseEntity response = restClient + .method(HttpMethod.POST) + .uri("http://mockbin.com/har") + .header("content-type", "multipart/form-data; boundary=---011000010111000001101001") + .contentType(MediaType.parseMediaType("multipart/form-data")) + .body("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"foo\"; filename=\"hello.txt\"\r\nContent-Type: text/plain\r\n\r\n\r\n-----011000010111000001101001--\r\n") + .retrieve() + .toEntity(String.class); \ No newline at end of file diff --git a/test/fixtures/output/java/restclient/multipart-form-data.java b/test/fixtures/output/java/restclient/multipart-form-data.java new file mode 100644 index 0000000..c1422d0 --- /dev/null +++ b/test/fixtures/output/java/restclient/multipart-form-data.java @@ -0,0 +1,10 @@ +RestClient restClient = RestClient.create(); + +ResponseEntity response = restClient + .method(HttpMethod.POST) + .uri("http://mockbin.com/har") + .header("Content-Type", "multipart/form-data; boundary=---011000010111000001101001") + .contentType(MediaType.parseMediaType("multipart/form-data")) + .body("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"foo\"\r\n\r\nbar\r\n-----011000010111000001101001--\r\n") + .retrieve() + .toEntity(String.class); \ No newline at end of file diff --git a/test/fixtures/output/java/restclient/nested.java b/test/fixtures/output/java/restclient/nested.java new file mode 100644 index 0000000..1a11770 --- /dev/null +++ b/test/fixtures/output/java/restclient/nested.java @@ -0,0 +1,7 @@ +RestClient restClient = RestClient.create(); + +ResponseEntity response = restClient + .method(HttpMethod.GET) + .uri("http://mockbin.com/har?foo%5Bbar%5D=baz%2Czap&fiz=buz&key=value") + .retrieve() + .toEntity(String.class); \ No newline at end of file diff --git a/test/fixtures/output/java/restclient/query.java b/test/fixtures/output/java/restclient/query.java new file mode 100644 index 0000000..f7df5fd --- /dev/null +++ b/test/fixtures/output/java/restclient/query.java @@ -0,0 +1,7 @@ +RestClient restClient = RestClient.create(); + +ResponseEntity response = restClient + .method(HttpMethod.GET) + .uri("http://mockbin.com/har?foo=bar&foo=baz&baz=abc&key=value") + .retrieve() + .toEntity(String.class); \ No newline at end of file diff --git a/test/fixtures/output/java/restclient/short.java b/test/fixtures/output/java/restclient/short.java new file mode 100644 index 0000000..b100f40 --- /dev/null +++ b/test/fixtures/output/java/restclient/short.java @@ -0,0 +1,7 @@ +RestClient restClient = RestClient.create(); + +ResponseEntity response = restClient + .method(HttpMethod.GET) + .uri("http://mockbin.com/har") + .retrieve() + .toEntity(String.class); \ No newline at end of file diff --git a/test/fixtures/output/java/restclient/text-plain.java b/test/fixtures/output/java/restclient/text-plain.java new file mode 100644 index 0000000..1a64732 --- /dev/null +++ b/test/fixtures/output/java/restclient/text-plain.java @@ -0,0 +1,10 @@ +RestClient restClient = RestClient.create(); + +ResponseEntity response = restClient + .method(HttpMethod.POST) + .uri("http://mockbin.com/har") + .header("content-type", "text/plain") + .contentType(MediaType.parseMediaType("text/plain")) + .body("Hello World") + .retrieve() + .toEntity(String.class); \ No newline at end of file diff --git a/test/fixtures/output/java/restclient/unparseable-query.java b/test/fixtures/output/java/restclient/unparseable-query.java new file mode 100644 index 0000000..6d64f6c --- /dev/null +++ b/test/fixtures/output/java/restclient/unparseable-query.java @@ -0,0 +1,7 @@ +RestClient restClient = RestClient.create(); + +ResponseEntity response = restClient + .method(HttpMethod.GET) + .uri("http://mockbin.com/har?&&a=b&&") + .retrieve() + .toEntity(String.class); \ No newline at end of file diff --git a/test/targets/java/restclient.js b/test/targets/java/restclient.js new file mode 100644 index 0000000..9ad8c17 --- /dev/null +++ b/test/targets/java/restclient.js @@ -0,0 +1,5 @@ +'use strict' + +require('should') + +module.exports = function (snippet, fixtures) {} From f1b2251729e5284b9500872858d0ea26f3187ec5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Mart=C3=ADnez=20Rinc=C3=B3n?= Date: Sun, 2 Nov 2025 23:12:13 +0100 Subject: [PATCH 2/7] refactor: query and content-type --- src/targets/java/restclient.js | 76 ++++++++++++++----- .../fixtures/output/java/restclient/full.java | 8 +- .../output/java/restclient/malicious.java | 30 +++++++- .../java/restclient/multipart-data.java | 4 +- .../java/restclient/multipart-file.java | 4 +- .../java/restclient/multipart-form-data.java | 4 +- .../output/java/restclient/nested.java | 9 ++- .../output/java/restclient/query.java | 10 ++- .../output/java/restclient/text-plain.java | 4 +- 9 files changed, 115 insertions(+), 34 deletions(-) diff --git a/src/targets/java/restclient.js b/src/targets/java/restclient.js index dd98097..96bfebd 100644 --- a/src/targets/java/restclient.js +++ b/src/targets/java/restclient.js @@ -12,8 +12,39 @@ const CodeBuilder = require('../../helpers/code-builder') +// Based off org.springframework.http.HttpMethod const standardMethods = ['GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS', 'TRACE'] +// Based off org.springframework.http.MediaType +const standardMediaTypes = { + 'application/atom+xml': 'APPLICATION_ATOM_XML', + 'application/cbor': 'APPLICATION_CBOR', + 'application/x-www-form-urlencoded': 'APPLICATION_FORM_URLENCODED', + 'application/graphql-response+json': 'APPLICATION_GRAPHQL_RESPONSE', + 'application/json': 'APPLICATION_JSON', + 'application/x-ndjson': 'APPLICATION_NDJSON', + 'application/octet-stream': 'APPLICATION_OCTET_STREAM', + 'application/pdf': 'APPLICATION_PDF', + 'application/problem+json': 'APPLICATION_PROBLEM_JSON', + 'application/problem+xml': 'APPLICATION_PROBLEM_XML', + 'application/x-protobuf': 'APPLICATION_PROTOBUF', + 'application/rss+xml': 'APPLICATION_RSS_XML', + 'application/xhtml+xml': 'APPLICATION_XHTML_XML', + 'application/xml': 'APPLICATION_XML', + 'application/yaml': 'APPLICATION_YAML', + 'image/gif': 'IMAGE_GIF', + 'image/jpeg': 'IMAGE_JPEG', + 'image/png': 'IMAGE_PNG', + 'multipart/form-data': 'MULTIPART_FORM_DATA', + 'multipart/mixed': 'MULTIPART_MIXED', + 'multipart/related': 'MULTIPART_RELATED', + 'text/event-stream': 'TEXT_EVENT_STREAM', + 'text/html': 'TEXT_HTML', + 'text/markdown': 'TEXT_MARKDOWN', + 'text/plain': 'TEXT_PLAIN', + 'text/xml': 'TEXT_XML' +} + module.exports = function (source, options) { const opts = Object.assign({ indent: ' ' @@ -32,39 +63,48 @@ module.exports = function (source, options) { code.push(1, '.method(HttpMethod.valueOf("%s"))', source.method.toUpperCase()) } - code.push(1, '.uri("%s")', source.fullUrl) + if (Object.keys(source.queryObj).length) { + code.push(1, '.uri("%s", uriBuilder -> {', source.url) + Object.keys(source.queryObj).forEach(function (key) { + const value = source.queryObj[key] + if (Array.isArray(value)) { + value.forEach(function (val) { + code.push(2, 'uriBuilder.queryParam("%qd", "%qd");', key, val) + }) + } else { + code.push(2, 'uriBuilder.queryParam("%qd", "%qd");', key, value) + } + }) + code.push(2, 'return uriBuilder.build();') + code.push(1, '})') + } else { + code.push(1, '.uri("%s")', source.url) + } if (source.cookies && source.cookies.length) { source.cookies.forEach(function (cookie) { - code.push(1, '.cookie("%s", "%s")', cookie.name, cookie.value) + code.push(1, '.cookie("%qd", "%qd")', cookie.name, cookie.value) }) } - const headers = Object.keys(source.allHeaders).filter(function (key) { - return key.toLowerCase() !== 'cookie' - }) + const headers = Object.keys(source.headersObj) if (headers.length) { headers.forEach(function (key) { - code.push(1, '.header("%s", "%qd")', key, source.allHeaders[key]) + code.push(1, '.header("%s", "%qd")', key, source.headersObj[key]) }) } if (source.postData && source.postData.text) { - if (source.postData.mimeType === 'application/json') { - code.push(1, '.contentType(MediaType.APPLICATION_JSON)') - code.push(1, '.body(%s)', JSON.stringify(source.postData.text)) - } else if (source.postData.mimeType === 'application/x-www-form-urlencoded') { - code.push(1, '.contentType(MediaType.APPLICATION_FORM_URLENCODED)') - code.push(1, '.body(%s)', JSON.stringify(source.postData.text)) - } else if (source.postData.mimeType && source.postData.mimeType.startsWith('multipart/form-data')) { - code.push(1, '.contentType(MediaType.parseMediaType("multipart/form-data"))') - code.push(1, '.body(%s)', JSON.stringify(source.postData.text)) - } else { - if (source.postData.mimeType) { + if (source.postData.mimeType) { + const mappedEnumConst = standardMediaTypes[source.postData.mimeType] + if (mappedEnumConst) { + code.push(1, '.contentType(MediaType.%s)', mappedEnumConst) + } else { code.push(1, '.contentType(MediaType.parseMediaType("%s"))', source.postData.mimeType) } - code.push(1, '.body(%s)', JSON.stringify(source.postData.text)) } + + code.push(1, '.body(%s)', JSON.stringify(source.postData.text)) } code.push(1, '.retrieve()') diff --git a/test/fixtures/output/java/restclient/full.java b/test/fixtures/output/java/restclient/full.java index 83327f0..6f2110c 100644 --- a/test/fixtures/output/java/restclient/full.java +++ b/test/fixtures/output/java/restclient/full.java @@ -2,7 +2,13 @@ ResponseEntity response = restClient .method(HttpMethod.POST) - .uri("http://mockbin.com/har?foo=bar&foo=baz&baz=abc&key=value") + .uri("http://mockbin.com/har", uriBuilder -> { + uriBuilder.queryParam("foo", "bar"); + uriBuilder.queryParam("foo", "baz"); + uriBuilder.queryParam("baz", "abc"); + uriBuilder.queryParam("key", "value"); + return uriBuilder.build(); + }) .cookie("foo", "bar") .cookie("bar", "baz") .header("accept", "application/json") diff --git a/test/fixtures/output/java/restclient/malicious.java b/test/fixtures/output/java/restclient/malicious.java index 535a860..9919ca1 100644 --- a/test/fixtures/output/java/restclient/malicious.java +++ b/test/fixtures/output/java/restclient/malicious.java @@ -2,7 +2,31 @@ ResponseEntity response = restClient .method(HttpMethod.POST) - .uri("http://example.test/%27%22%60$(%(%%7B%7B%7B/0%s//?'=squote-key-test&squote-value-test='&%22=dquote-key-test&dquote-value-test=%22&%60=backtick-key-test&backtick-value-test=%60&%24(=dollar-parenthesis-key-test&dollar-parenthesis-value-test=%24(&%23%7B=hash-brace-key-test&hash-brace-value-test=%23%7B&%25(=percent-parenthesis-key-test&percent-parenthesis-value-test=%25(&%25%7B=percent-brace-key-test&percent-brace-value-test=%25%7B&%7B%7B=double-brace-key-test&double-brace-value-test=%7B%7B&%5C0=null-key-test&null-value-test=%5C0&%25s=string-fmt-key-test&string-fmt-value-test=%25s&%5C=slash-key-test&slash-value-test=%5C") + .uri("http://example.test/%27%22%60$(%(%%7B%7B%7B/0%s//", uriBuilder -> { + uriBuilder.queryParam("'", "squote-key-test"); + uriBuilder.queryParam("squote-value-test", "'"); + uriBuilder.queryParam("\"", "dquote-key-test"); + uriBuilder.queryParam("dquote-value-test", "\""); + uriBuilder.queryParam("`", "backtick-key-test"); + uriBuilder.queryParam("backtick-value-test", "`"); + uriBuilder.queryParam("$(", "dollar-parenthesis-key-test"); + uriBuilder.queryParam("dollar-parenthesis-value-test", "$("); + uriBuilder.queryParam("#{", "hash-brace-key-test"); + uriBuilder.queryParam("hash-brace-value-test", "#{"); + uriBuilder.queryParam("%(", "percent-parenthesis-key-test"); + uriBuilder.queryParam("percent-parenthesis-value-test", "%("); + uriBuilder.queryParam("%{", "percent-brace-key-test"); + uriBuilder.queryParam("percent-brace-value-test", "%{"); + uriBuilder.queryParam("{{", "double-brace-key-test"); + uriBuilder.queryParam("double-brace-value-test", "{{"); + uriBuilder.queryParam("\\0", "null-key-test"); + uriBuilder.queryParam("null-value-test", "\\0"); + uriBuilder.queryParam("%s", "string-fmt-key-test"); + uriBuilder.queryParam("string-fmt-value-test", "%s"); + uriBuilder.queryParam("\\", "slash-key-test"); + uriBuilder.queryParam("slash-value-test", "\\"); + return uriBuilder.build(); + }) .header("'", "squote-key-test") .header("squote-value-test", "'") .header("dquote-value-test", "\"") @@ -19,7 +43,7 @@ .header("null-value-test", "\\0") .header("string-fmt-value-test", "%s") .header("slash-value-test", "\\") - .contentType(MediaType.parseMediaType("text/plain")) + .contentType(MediaType.TEXT_PLAIN) .body("' \" ` $( #{ %( %{ {{ \\0 %s \\") .retrieve() - .toEntity(String.class); \ No newline at end of file + .toEntity(String.class); diff --git a/test/fixtures/output/java/restclient/multipart-data.java b/test/fixtures/output/java/restclient/multipart-data.java index 842ac6e..611d584 100644 --- a/test/fixtures/output/java/restclient/multipart-data.java +++ b/test/fixtures/output/java/restclient/multipart-data.java @@ -4,7 +4,7 @@ .method(HttpMethod.POST) .uri("http://mockbin.com/har") .header("content-type", "multipart/form-data; boundary=---011000010111000001101001") - .contentType(MediaType.parseMediaType("multipart/form-data")) + .contentType(MediaType.MULTIPART_FORM_DATA) .body("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"foo\"; filename=\"hello.txt\"\r\nContent-Type: text/plain\r\n\r\nHello World\r\n-----011000010111000001101001--\r\n") .retrieve() - .toEntity(String.class); \ No newline at end of file + .toEntity(String.class); diff --git a/test/fixtures/output/java/restclient/multipart-file.java b/test/fixtures/output/java/restclient/multipart-file.java index bc60ffe..2354491 100644 --- a/test/fixtures/output/java/restclient/multipart-file.java +++ b/test/fixtures/output/java/restclient/multipart-file.java @@ -4,7 +4,7 @@ .method(HttpMethod.POST) .uri("http://mockbin.com/har") .header("content-type", "multipart/form-data; boundary=---011000010111000001101001") - .contentType(MediaType.parseMediaType("multipart/form-data")) + .contentType(MediaType.MULTIPART_FORM_DATA) .body("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"foo\"; filename=\"hello.txt\"\r\nContent-Type: text/plain\r\n\r\n\r\n-----011000010111000001101001--\r\n") .retrieve() - .toEntity(String.class); \ No newline at end of file + .toEntity(String.class); diff --git a/test/fixtures/output/java/restclient/multipart-form-data.java b/test/fixtures/output/java/restclient/multipart-form-data.java index c1422d0..4fe35c6 100644 --- a/test/fixtures/output/java/restclient/multipart-form-data.java +++ b/test/fixtures/output/java/restclient/multipart-form-data.java @@ -4,7 +4,7 @@ .method(HttpMethod.POST) .uri("http://mockbin.com/har") .header("Content-Type", "multipart/form-data; boundary=---011000010111000001101001") - .contentType(MediaType.parseMediaType("multipart/form-data")) + .contentType(MediaType.MULTIPART_FORM_DATA) .body("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"foo\"\r\n\r\nbar\r\n-----011000010111000001101001--\r\n") .retrieve() - .toEntity(String.class); \ No newline at end of file + .toEntity(String.class); diff --git a/test/fixtures/output/java/restclient/nested.java b/test/fixtures/output/java/restclient/nested.java index 1a11770..53b6846 100644 --- a/test/fixtures/output/java/restclient/nested.java +++ b/test/fixtures/output/java/restclient/nested.java @@ -2,6 +2,11 @@ ResponseEntity response = restClient .method(HttpMethod.GET) - .uri("http://mockbin.com/har?foo%5Bbar%5D=baz%2Czap&fiz=buz&key=value") + .uri("http://mockbin.com/har", uriBuilder -> { + uriBuilder.queryParam("foo[bar]", "baz,zap"); + uriBuilder.queryParam("fiz", "buz"); + uriBuilder.queryParam("key", "value"); + return uriBuilder.build(); + }) .retrieve() - .toEntity(String.class); \ No newline at end of file + .toEntity(String.class); diff --git a/test/fixtures/output/java/restclient/query.java b/test/fixtures/output/java/restclient/query.java index f7df5fd..7979608 100644 --- a/test/fixtures/output/java/restclient/query.java +++ b/test/fixtures/output/java/restclient/query.java @@ -2,6 +2,12 @@ ResponseEntity response = restClient .method(HttpMethod.GET) - .uri("http://mockbin.com/har?foo=bar&foo=baz&baz=abc&key=value") + .uri("http://mockbin.com/har", uriBuilder -> { + uriBuilder.queryParam("foo", "bar"); + uriBuilder.queryParam("foo", "baz"); + uriBuilder.queryParam("baz", "abc"); + uriBuilder.queryParam("key", "value"); + return uriBuilder.build(); + }) .retrieve() - .toEntity(String.class); \ No newline at end of file + .toEntity(String.class); diff --git a/test/fixtures/output/java/restclient/text-plain.java b/test/fixtures/output/java/restclient/text-plain.java index 1a64732..1738077 100644 --- a/test/fixtures/output/java/restclient/text-plain.java +++ b/test/fixtures/output/java/restclient/text-plain.java @@ -4,7 +4,7 @@ .method(HttpMethod.POST) .uri("http://mockbin.com/har") .header("content-type", "text/plain") - .contentType(MediaType.parseMediaType("text/plain")) + .contentType(MediaType.TEXT_PLAIN) .body("Hello World") .retrieve() - .toEntity(String.class); \ No newline at end of file + .toEntity(String.class); From cd53b68efa15edccbdd4265113974655f07c09de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Mart=C3=ADnez=20Rinc=C3=B3n?= Date: Sun, 2 Nov 2025 23:19:46 +0100 Subject: [PATCH 3/7] feat: add option for specifying returned entity class --- src/targets/java/restclient.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/targets/java/restclient.js b/src/targets/java/restclient.js index 96bfebd..055f634 100644 --- a/src/targets/java/restclient.js +++ b/src/targets/java/restclient.js @@ -47,7 +47,8 @@ const standardMediaTypes = { module.exports = function (source, options) { const opts = Object.assign({ - indent: ' ' + indent: ' ', + entityClass: 'String' }, options) const code = new CodeBuilder(opts.indent) @@ -55,7 +56,7 @@ module.exports = function (source, options) { code.push('RestClient restClient = RestClient.create();') .blank() - code.push('ResponseEntity response = restClient') + code.push('ResponseEntity<%s> response = restClient', opts.entityClass) if (standardMethods.includes(source.method.toUpperCase())) { code.push(1, '.method(HttpMethod.%s)', source.method.toUpperCase()) @@ -108,7 +109,7 @@ module.exports = function (source, options) { } code.push(1, '.retrieve()') - code.push(1, '.toEntity(String.class);') + code.push(1, '.toEntity(%s.class);', opts.entityClass) return code.join() } From 1248f431b322b14a987111b52b7604edf40b3e19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Mart=C3=ADnez=20Rinc=C3=B3n?= Date: Mon, 3 Nov 2025 00:08:58 +0100 Subject: [PATCH 4/7] refactor: support multipart and general cleanup --- src/targets/java/restclient.js | 82 ++++++++++++++++--- .../restclient/application-form-encoded.java | 9 +- .../java/restclient/application-json.java | 3 +- .../fixtures/output/java/restclient/full.java | 6 +- .../java/restclient/jsonObj-multiline.java | 3 +- .../java/restclient/jsonObj-null-value.java | 3 +- .../java/restclient/multipart-data.java | 8 +- .../java/restclient/multipart-file.java | 7 +- .../java/restclient/multipart-form-data.java | 6 +- .../output/java/restclient/text-plain.java | 1 - 10 files changed, 98 insertions(+), 30 deletions(-) diff --git a/src/targets/java/restclient.js b/src/targets/java/restclient.js index 055f634..9d6a2db 100644 --- a/src/targets/java/restclient.js +++ b/src/targets/java/restclient.js @@ -12,10 +12,8 @@ const CodeBuilder = require('../../helpers/code-builder') -// Based off org.springframework.http.HttpMethod const standardMethods = ['GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS', 'TRACE'] -// Based off org.springframework.http.MediaType const standardMediaTypes = { 'application/atom+xml': 'APPLICATION_ATOM_XML', 'application/cbor': 'APPLICATION_CBOR', @@ -45,6 +43,20 @@ const standardMediaTypes = { 'text/xml': 'TEXT_XML' } +const jsonMimeTypes = [ + 'application/json', + 'text/json', + 'text/x-json', + 'application/x-json' +] + +const multipartMimeTypes = [ + 'multipart/form-data', + 'multipart/mixed', + 'multipart/related', + 'multipart/alternative' +] + module.exports = function (source, options) { const opts = Object.assign({ indent: ' ', @@ -56,6 +68,43 @@ module.exports = function (source, options) { code.push('RestClient restClient = RestClient.create();') .blank() + if (source.postData && source.postData.mimeType === 'application/x-www-form-urlencoded' && source.postData.params) { + code.push('MultiValueMap formDataMap = new LinkedMultiValueMap<>();') + source.postData.params.forEach(function (param) { + code.push('formDataMap.add("%qd", "%qd");', param.name, param.value) + }) + code.blank() + } + + if (source.postData && multipartMimeTypes.includes(source.postData.mimeType) && source.postData.params) { + code.push('MultipartBodyBuilder multipartBuilder = new MultipartBodyBuilder();') + + source.postData.params.forEach(function (param) { + if (param.fileName) { + if (param.value) { + code.push('multipartBuilder.part("%s", "%qd")', param.name, param.value) + code.push(1, '.filename("%s")', param.fileName) + } else { + code.push('multipartBuilder.part("%s", new FileSystemResource("%s"))', param.name, param.fileName) + } + + if (param.contentType) { + const mediaTypeConstant = standardMediaTypes[param.contentType] + if (mediaTypeConstant) { + code.push(1, '.contentType(MediaType.%s);', mediaTypeConstant) + } else { + code.push(1, '.contentType(MediaType.parseMediaType("%s"));', param.contentType) + } + } else { + code.push(1, ';') + } + } else { + code.push('multipartBuilder.part("%s", "%qd");', param.name, param.value || '') + } + }) + code.blank() + } + code.push('ResponseEntity<%s> response = restClient', opts.entityClass) if (standardMethods.includes(source.method.toUpperCase())) { @@ -91,21 +140,30 @@ module.exports = function (source, options) { const headers = Object.keys(source.headersObj) if (headers.length) { headers.forEach(function (key) { - code.push(1, '.header("%s", "%qd")', key, source.headersObj[key]) + if (key.toLowerCase() !== 'content-type') { + code.push(1, '.header("%s", "%qd")', key, source.headersObj[key]) + } }) } - if (source.postData && source.postData.text) { - if (source.postData.mimeType) { - const mappedEnumConst = standardMediaTypes[source.postData.mimeType] - if (mappedEnumConst) { - code.push(1, '.contentType(MediaType.%s)', mappedEnumConst) - } else { - code.push(1, '.contentType(MediaType.parseMediaType("%s"))', source.postData.mimeType) - } + if (source.postData && (source.postData.params || source.postData.text)) { + const mediaTypeEnumConstant = standardMediaTypes[source.postData.mimeType] + + if (mediaTypeEnumConstant) { + code.push(1, '.contentType(MediaType.%s)', mediaTypeEnumConstant) + } else { + code.push(1, '.contentType(MediaType.parseMediaType("%s"))', source.postData.mimeType) } - code.push(1, '.body(%s)', JSON.stringify(source.postData.text)) + if (source.postData.mimeType === 'application/x-www-form-urlencoded' && source.postData.params) { + code.push(1, '.body(formDataMap)') + } else if (multipartMimeTypes.includes(source.postData.mimeType) && source.postData.params) { + code.push(1, '.body(multipartBuilder.build())') + } else if (source.postData.text) { + code.push(1, '.body(%s)', JSON.stringify(source.postData.text)) + } else if (source.postData.jsonObj && jsonMimeTypes.includes(source.postData.mimeType)) { + code.push(1, '.body(%s)', JSON.stringify(JSON.stringify(source.postData.jsonObj))) + } } code.push(1, '.retrieve()') diff --git a/test/fixtures/output/java/restclient/application-form-encoded.java b/test/fixtures/output/java/restclient/application-form-encoded.java index a3b49de..63b17ff 100644 --- a/test/fixtures/output/java/restclient/application-form-encoded.java +++ b/test/fixtures/output/java/restclient/application-form-encoded.java @@ -1,10 +1,13 @@ RestClient restClient = RestClient.create(); +MultiValueMap formDataMap = new LinkedMultiValueMap<>(); +formDataMap.add("foo", "bar"); +formDataMap.add("hello", "world"); + ResponseEntity response = restClient .method(HttpMethod.POST) .uri("http://mockbin.com/har") - .header("content-type", "application/x-www-form-urlencoded") .contentType(MediaType.APPLICATION_FORM_URLENCODED) - .body("foo=bar&hello=world") + .body(formDataMap) .retrieve() - .toEntity(String.class); \ No newline at end of file + .toEntity(String.class); diff --git a/test/fixtures/output/java/restclient/application-json.java b/test/fixtures/output/java/restclient/application-json.java index ebd7a8e..5d12031 100644 --- a/test/fixtures/output/java/restclient/application-json.java +++ b/test/fixtures/output/java/restclient/application-json.java @@ -3,8 +3,7 @@ ResponseEntity response = restClient .method(HttpMethod.POST) .uri("http://mockbin.com/har") - .header("content-type", "application/json") .contentType(MediaType.APPLICATION_JSON) .body("{\"number\":1,\"string\":\"f\\\"oo\",\"arr\":[1,2,3],\"nested\":{\"a\":\"b\"},\"arr_mix\":[1,\"a\",{\"arr_mix_nested\":{}}],\"boolean\":false}") .retrieve() - .toEntity(String.class); \ No newline at end of file + .toEntity(String.class); diff --git a/test/fixtures/output/java/restclient/full.java b/test/fixtures/output/java/restclient/full.java index 6f2110c..8126f6d 100644 --- a/test/fixtures/output/java/restclient/full.java +++ b/test/fixtures/output/java/restclient/full.java @@ -1,5 +1,8 @@ RestClient restClient = RestClient.create(); +MultiValueMap formDataMap = new LinkedMultiValueMap<>(); +formDataMap.add("foo", "bar"); + ResponseEntity response = restClient .method(HttpMethod.POST) .uri("http://mockbin.com/har", uriBuilder -> { @@ -12,8 +15,7 @@ .cookie("foo", "bar") .cookie("bar", "baz") .header("accept", "application/json") - .header("content-type", "application/x-www-form-urlencoded") .contentType(MediaType.APPLICATION_FORM_URLENCODED) - .body("foo=bar") + .body(formDataMap) .retrieve() .toEntity(String.class); diff --git a/test/fixtures/output/java/restclient/jsonObj-multiline.java b/test/fixtures/output/java/restclient/jsonObj-multiline.java index ae0c92b..71a2fbe 100644 --- a/test/fixtures/output/java/restclient/jsonObj-multiline.java +++ b/test/fixtures/output/java/restclient/jsonObj-multiline.java @@ -3,8 +3,7 @@ ResponseEntity response = restClient .method(HttpMethod.POST) .uri("http://mockbin.com/har") - .header("content-type", "application/json") .contentType(MediaType.APPLICATION_JSON) .body("{\n \"foo\": \"bar\"\n}") .retrieve() - .toEntity(String.class); \ No newline at end of file + .toEntity(String.class); diff --git a/test/fixtures/output/java/restclient/jsonObj-null-value.java b/test/fixtures/output/java/restclient/jsonObj-null-value.java index f1b6a21..8b3f17b 100644 --- a/test/fixtures/output/java/restclient/jsonObj-null-value.java +++ b/test/fixtures/output/java/restclient/jsonObj-null-value.java @@ -3,8 +3,7 @@ ResponseEntity response = restClient .method(HttpMethod.POST) .uri("http://mockbin.com/har") - .header("content-type", "application/json") .contentType(MediaType.APPLICATION_JSON) .body("{\"foo\":null}") .retrieve() - .toEntity(String.class); \ No newline at end of file + .toEntity(String.class); diff --git a/test/fixtures/output/java/restclient/multipart-data.java b/test/fixtures/output/java/restclient/multipart-data.java index 611d584..ef5d723 100644 --- a/test/fixtures/output/java/restclient/multipart-data.java +++ b/test/fixtures/output/java/restclient/multipart-data.java @@ -1,10 +1,14 @@ RestClient restClient = RestClient.create(); +MultipartBodyBuilder multipartBuilder = new MultipartBodyBuilder(); +multipartBuilder.part("foo", "Hello World") + .filename("hello.txt") + .contentType(MediaType.TEXT_PLAIN); + ResponseEntity response = restClient .method(HttpMethod.POST) .uri("http://mockbin.com/har") - .header("content-type", "multipart/form-data; boundary=---011000010111000001101001") .contentType(MediaType.MULTIPART_FORM_DATA) - .body("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"foo\"; filename=\"hello.txt\"\r\nContent-Type: text/plain\r\n\r\nHello World\r\n-----011000010111000001101001--\r\n") + .body(multipartBuilder.build()) .retrieve() .toEntity(String.class); diff --git a/test/fixtures/output/java/restclient/multipart-file.java b/test/fixtures/output/java/restclient/multipart-file.java index 2354491..1144288 100644 --- a/test/fixtures/output/java/restclient/multipart-file.java +++ b/test/fixtures/output/java/restclient/multipart-file.java @@ -1,10 +1,13 @@ RestClient restClient = RestClient.create(); +MultipartBodyBuilder multipartBuilder = new MultipartBodyBuilder(); +multipartBuilder.part("foo", new FileSystemResource("test/fixtures/files/hello.txt")) + .contentType(MediaType.TEXT_PLAIN); + ResponseEntity response = restClient .method(HttpMethod.POST) .uri("http://mockbin.com/har") - .header("content-type", "multipart/form-data; boundary=---011000010111000001101001") .contentType(MediaType.MULTIPART_FORM_DATA) - .body("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"foo\"; filename=\"hello.txt\"\r\nContent-Type: text/plain\r\n\r\n\r\n-----011000010111000001101001--\r\n") + .body(multipartBuilder.build()) .retrieve() .toEntity(String.class); diff --git a/test/fixtures/output/java/restclient/multipart-form-data.java b/test/fixtures/output/java/restclient/multipart-form-data.java index 4fe35c6..604d426 100644 --- a/test/fixtures/output/java/restclient/multipart-form-data.java +++ b/test/fixtures/output/java/restclient/multipart-form-data.java @@ -1,10 +1,12 @@ RestClient restClient = RestClient.create(); +MultipartBodyBuilder multipartBuilder = new MultipartBodyBuilder(); +multipartBuilder.part("foo", "bar"); + ResponseEntity response = restClient .method(HttpMethod.POST) .uri("http://mockbin.com/har") - .header("Content-Type", "multipart/form-data; boundary=---011000010111000001101001") .contentType(MediaType.MULTIPART_FORM_DATA) - .body("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"foo\"\r\n\r\nbar\r\n-----011000010111000001101001--\r\n") + .body(multipartBuilder.build()) .retrieve() .toEntity(String.class); diff --git a/test/fixtures/output/java/restclient/text-plain.java b/test/fixtures/output/java/restclient/text-plain.java index 1738077..8aef2bf 100644 --- a/test/fixtures/output/java/restclient/text-plain.java +++ b/test/fixtures/output/java/restclient/text-plain.java @@ -3,7 +3,6 @@ ResponseEntity response = restClient .method(HttpMethod.POST) .uri("http://mockbin.com/har") - .header("content-type", "text/plain") .contentType(MediaType.TEXT_PLAIN) .body("Hello World") .retrieve() From 24ed2ab8ddc817a652967853a5dd4823fcc5a949 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Mart=C3=ADnez=20Rinc=C3=B3n?= Date: Mon, 3 Nov 2025 00:54:05 +0100 Subject: [PATCH 5/7] chore: rename option for entity class and document it --- TARGETS.md | 7 ++++--- src/targets/java/restclient.js | 6 +++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/TARGETS.md b/TARGETS.md index 786fcc4..e855002 100644 --- a/TARGETS.md +++ b/TARGETS.md @@ -46,9 +46,10 @@ Currently the following output targets are supported: ###### Options -| Option | Default | Description | -| --------- | ------- | -------------------------------- | -| `indent` | ` ` | line break & indent output value | +| Option | Default | Description | +| ------------ | -------- | -------------------------------- | +| `indent` | ` ` | line break & indent output value | +| `entityType` | `String` | Java type for the entity | ---- diff --git a/src/targets/java/restclient.js b/src/targets/java/restclient.js index 9d6a2db..07b672c 100644 --- a/src/targets/java/restclient.js +++ b/src/targets/java/restclient.js @@ -60,7 +60,7 @@ const multipartMimeTypes = [ module.exports = function (source, options) { const opts = Object.assign({ indent: ' ', - entityClass: 'String' + entityType: 'String' }, options) const code = new CodeBuilder(opts.indent) @@ -105,7 +105,7 @@ module.exports = function (source, options) { code.blank() } - code.push('ResponseEntity<%s> response = restClient', opts.entityClass) + code.push('ResponseEntity<%s> response = restClient', opts.entityType) if (standardMethods.includes(source.method.toUpperCase())) { code.push(1, '.method(HttpMethod.%s)', source.method.toUpperCase()) @@ -167,7 +167,7 @@ module.exports = function (source, options) { } code.push(1, '.retrieve()') - code.push(1, '.toEntity(%s.class);', opts.entityClass) + code.push(1, '.toEntity(%s.class);', opts.entityType) return code.join() } From 7b5e1180d5ecf45712bd07cd947bc1779d99a8b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Mart=C3=ADnez=20Rinc=C3=B3n?= Date: Mon, 3 Nov 2025 21:45:49 +0100 Subject: [PATCH 6/7] refactor: body building --- src/targets/java/restclient.js | 101 +++++++++++++++++---------------- 1 file changed, 53 insertions(+), 48 deletions(-) diff --git a/src/targets/java/restclient.js b/src/targets/java/restclient.js index 07b672c..78d4d74 100644 --- a/src/targets/java/restclient.js +++ b/src/targets/java/restclient.js @@ -43,13 +43,6 @@ const standardMediaTypes = { 'text/xml': 'TEXT_XML' } -const jsonMimeTypes = [ - 'application/json', - 'text/json', - 'text/x-json', - 'application/x-json' -] - const multipartMimeTypes = [ 'multipart/form-data', 'multipart/mixed', @@ -63,46 +56,59 @@ module.exports = function (source, options) { entityType: 'String' }, options) + const state = { + bodyType: null + } + const code = new CodeBuilder(opts.indent) code.push('RestClient restClient = RestClient.create();') .blank() - if (source.postData && source.postData.mimeType === 'application/x-www-form-urlencoded' && source.postData.params) { - code.push('MultiValueMap formDataMap = new LinkedMultiValueMap<>();') - source.postData.params.forEach(function (param) { - code.push('formDataMap.add("%qd", "%qd");', param.name, param.value) - }) - code.blank() - } + if (source.postData) { + if (source.postData.params && source.postData.mimeType === 'application/x-www-form-urlencoded') { + state.bodyType = 'form' - if (source.postData && multipartMimeTypes.includes(source.postData.mimeType) && source.postData.params) { - code.push('MultipartBodyBuilder multipartBuilder = new MultipartBodyBuilder();') + code.push('MultiValueMap formDataMap = new LinkedMultiValueMap<>();') - source.postData.params.forEach(function (param) { - if (param.fileName) { - if (param.value) { - code.push('multipartBuilder.part("%s", "%qd")', param.name, param.value) - code.push(1, '.filename("%s")', param.fileName) - } else { - code.push('multipartBuilder.part("%s", new FileSystemResource("%s"))', param.name, param.fileName) - } + source.postData.params.forEach(function (param) { + code.push('formDataMap.add("%qd", "%qd");', param.name, param.value) + }) + + code.blank() + } else if (source.postData.params && multipartMimeTypes.includes(source.postData.mimeType)) { + state.bodyType = 'multipart' - if (param.contentType) { - const mediaTypeConstant = standardMediaTypes[param.contentType] - if (mediaTypeConstant) { - code.push(1, '.contentType(MediaType.%s);', mediaTypeConstant) + code.push('MultipartBodyBuilder multipartBuilder = new MultipartBodyBuilder();') + + source.postData.params.forEach(function (param) { + if (param.fileName) { + if (param.value) { + code.push('multipartBuilder.part("%s", "%qd")', param.name, param.value) + code.push(1, '.filename("%s")', param.fileName) } else { - code.push(1, '.contentType(MediaType.parseMediaType("%s"));', param.contentType) + code.push('multipartBuilder.part("%s", new FileSystemResource("%s"))', param.name, param.fileName) + } + + if (param.contentType) { + const mediaTypeConstant = standardMediaTypes[param.contentType] + if (mediaTypeConstant) { + code.push(1, '.contentType(MediaType.%s);', mediaTypeConstant) + } else { + code.push(1, '.contentType(MediaType.parseMediaType("%s"));', param.contentType) + } + } else { + code.push(1, ';') } } else { - code.push(1, ';') + code.push('multipartBuilder.part("%s", "%qd");', param.name, param.value || '') } - } else { - code.push('multipartBuilder.part("%s", "%qd");', param.name, param.value || '') - } - }) - code.blank() + }) + + code.blank() + } else if (source.postData.text) { + state.bodyType = 'plaintext' + } } code.push('ResponseEntity<%s> response = restClient', opts.entityType) @@ -146,23 +152,22 @@ module.exports = function (source, options) { }) } - if (source.postData && (source.postData.params || source.postData.text)) { - const mediaTypeEnumConstant = standardMediaTypes[source.postData.mimeType] - - if (mediaTypeEnumConstant) { - code.push(1, '.contentType(MediaType.%s)', mediaTypeEnumConstant) - } else { - code.push(1, '.contentType(MediaType.parseMediaType("%s"))', source.postData.mimeType) + if (source.postData && state.bodyType) { + if (source.postData.mimeType) { + const mediaTypeEnumConstant = standardMediaTypes[source.postData.mimeType] + if (mediaTypeEnumConstant) { + code.push(1, '.contentType(MediaType.%s)', mediaTypeEnumConstant) + } else { + code.push(1, '.contentType(MediaType.parseMediaType("%s"))', source.postData.mimeType) + } } - if (source.postData.mimeType === 'application/x-www-form-urlencoded' && source.postData.params) { + if (state.bodyType === 'form') { code.push(1, '.body(formDataMap)') - } else if (multipartMimeTypes.includes(source.postData.mimeType) && source.postData.params) { + } else if (state.bodyType === 'multipart') { code.push(1, '.body(multipartBuilder.build())') - } else if (source.postData.text) { - code.push(1, '.body(%s)', JSON.stringify(source.postData.text)) - } else if (source.postData.jsonObj && jsonMimeTypes.includes(source.postData.mimeType)) { - code.push(1, '.body(%s)', JSON.stringify(JSON.stringify(source.postData.jsonObj))) + } else if (state.bodyType === 'plaintext') { + code.push(1, '.body("%qd")', source.postData.text) } } From 22ddd55aaad7389ecbe473bd2eb3a35f999fadf9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Mart=C3=ADnez=20Rinc=C3=B3n?= Date: Mon, 3 Nov 2025 22:39:15 +0100 Subject: [PATCH 7/7] refactor: dedup code --- src/targets/java/restclient.js | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/targets/java/restclient.js b/src/targets/java/restclient.js index 78d4d74..a4fbeb5 100644 --- a/src/targets/java/restclient.js +++ b/src/targets/java/restclient.js @@ -121,16 +121,15 @@ module.exports = function (source, options) { if (Object.keys(source.queryObj).length) { code.push(1, '.uri("%s", uriBuilder -> {', source.url) + Object.keys(source.queryObj).forEach(function (key) { const value = source.queryObj[key] - if (Array.isArray(value)) { - value.forEach(function (val) { - code.push(2, 'uriBuilder.queryParam("%qd", "%qd");', key, val) - }) - } else { - code.push(2, 'uriBuilder.queryParam("%qd", "%qd");', key, value) - } + const iterable = Array.isArray(value) ? value : [value] + iterable.forEach(function (val) { + code.push(2, 'uriBuilder.queryParam("%qd", "%qd");', key, val) + }) }) + code.push(2, 'return uriBuilder.build();') code.push(1, '})') } else {