diff --git a/TARGETS.md b/TARGETS.md index 08d9986..e855002 100644 --- a/TARGETS.md +++ b/TARGETS.md @@ -40,6 +40,17 @@ 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 | +| `entityType` | `String` | Java type for the entity | + ---- ## 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..a4fbeb5 --- /dev/null +++ b/src/targets/java/restclient.js @@ -0,0 +1,184 @@ +/** + * @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'] + +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' +} + +const multipartMimeTypes = [ + 'multipart/form-data', + 'multipart/mixed', + 'multipart/related', + 'multipart/alternative' +] + +module.exports = function (source, options) { + const opts = Object.assign({ + indent: ' ', + entityType: 'String' + }, options) + + const state = { + bodyType: null + } + + const code = new CodeBuilder(opts.indent) + + code.push('RestClient restClient = RestClient.create();') + .blank() + + if (source.postData) { + if (source.postData.params && source.postData.mimeType === 'application/x-www-form-urlencoded') { + state.bodyType = 'form' + + code.push('MultiValueMap formDataMap = new LinkedMultiValueMap<>();') + + 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' + + 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() + } else if (source.postData.text) { + state.bodyType = 'plaintext' + } + } + + code.push('ResponseEntity<%s> response = restClient', opts.entityType) + + 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()) + } + + 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] + 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 { + code.push(1, '.uri("%s")', source.url) + } + + if (source.cookies && source.cookies.length) { + source.cookies.forEach(function (cookie) { + code.push(1, '.cookie("%qd", "%qd")', cookie.name, cookie.value) + }) + } + + const headers = Object.keys(source.headersObj) + if (headers.length) { + headers.forEach(function (key) { + if (key.toLowerCase() !== 'content-type') { + code.push(1, '.header("%s", "%qd")', key, source.headersObj[key]) + } + }) + } + + 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 (state.bodyType === 'form') { + code.push(1, '.body(formDataMap)') + } else if (state.bodyType === 'multipart') { + code.push(1, '.body(multipartBuilder.build())') + } else if (state.bodyType === 'plaintext') { + code.push(1, '.body("%qd")', source.postData.text) + } + } + + code.push(1, '.retrieve()') + code.push(1, '.toEntity(%s.class);', opts.entityType) + + 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..63b17ff --- /dev/null +++ b/test/fixtures/output/java/restclient/application-form-encoded.java @@ -0,0 +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") + .contentType(MediaType.APPLICATION_FORM_URLENCODED) + .body(formDataMap) + .retrieve() + .toEntity(String.class); 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..5d12031 --- /dev/null +++ b/test/fixtures/output/java/restclient/application-json.java @@ -0,0 +1,9 @@ +RestClient restClient = RestClient.create(); + +ResponseEntity response = restClient + .method(HttpMethod.POST) + .uri("http://mockbin.com/har") + .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); 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..8126f6d --- /dev/null +++ b/test/fixtures/output/java/restclient/full.java @@ -0,0 +1,21 @@ +RestClient restClient = RestClient.create(); + +MultiValueMap formDataMap = new LinkedMultiValueMap<>(); +formDataMap.add("foo", "bar"); + +ResponseEntity response = restClient + .method(HttpMethod.POST) + .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") + .contentType(MediaType.APPLICATION_FORM_URLENCODED) + .body(formDataMap) + .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..71a2fbe --- /dev/null +++ b/test/fixtures/output/java/restclient/jsonObj-multiline.java @@ -0,0 +1,9 @@ +RestClient restClient = RestClient.create(); + +ResponseEntity response = restClient + .method(HttpMethod.POST) + .uri("http://mockbin.com/har") + .contentType(MediaType.APPLICATION_JSON) + .body("{\n \"foo\": \"bar\"\n}") + .retrieve() + .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 new file mode 100644 index 0000000..8b3f17b --- /dev/null +++ b/test/fixtures/output/java/restclient/jsonObj-null-value.java @@ -0,0 +1,9 @@ +RestClient restClient = RestClient.create(); + +ResponseEntity response = restClient + .method(HttpMethod.POST) + .uri("http://mockbin.com/har") + .contentType(MediaType.APPLICATION_JSON) + .body("{\"foo\":null}") + .retrieve() + .toEntity(String.class); diff --git a/test/fixtures/output/java/restclient/malicious.java b/test/fixtures/output/java/restclient/malicious.java new file mode 100644 index 0000000..9919ca1 --- /dev/null +++ b/test/fixtures/output/java/restclient/malicious.java @@ -0,0 +1,49 @@ +RestClient restClient = RestClient.create(); + +ResponseEntity response = restClient + .method(HttpMethod.POST) + .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", "\"") + .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.TEXT_PLAIN) + .body("' \" ` $( #{ %( %{ {{ \\0 %s \\") + .retrieve() + .toEntity(String.class); 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..ef5d723 --- /dev/null +++ b/test/fixtures/output/java/restclient/multipart-data.java @@ -0,0 +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") + .contentType(MediaType.MULTIPART_FORM_DATA) + .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 new file mode 100644 index 0000000..1144288 --- /dev/null +++ b/test/fixtures/output/java/restclient/multipart-file.java @@ -0,0 +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") + .contentType(MediaType.MULTIPART_FORM_DATA) + .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 new file mode 100644 index 0000000..604d426 --- /dev/null +++ b/test/fixtures/output/java/restclient/multipart-form-data.java @@ -0,0 +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") + .contentType(MediaType.MULTIPART_FORM_DATA) + .body(multipartBuilder.build()) + .retrieve() + .toEntity(String.class); diff --git a/test/fixtures/output/java/restclient/nested.java b/test/fixtures/output/java/restclient/nested.java new file mode 100644 index 0000000..53b6846 --- /dev/null +++ b/test/fixtures/output/java/restclient/nested.java @@ -0,0 +1,12 @@ +RestClient restClient = RestClient.create(); + +ResponseEntity response = restClient + .method(HttpMethod.GET) + .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); diff --git a/test/fixtures/output/java/restclient/query.java b/test/fixtures/output/java/restclient/query.java new file mode 100644 index 0000000..7979608 --- /dev/null +++ b/test/fixtures/output/java/restclient/query.java @@ -0,0 +1,13 @@ +RestClient restClient = RestClient.create(); + +ResponseEntity response = restClient + .method(HttpMethod.GET) + .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); 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..8aef2bf --- /dev/null +++ b/test/fixtures/output/java/restclient/text-plain.java @@ -0,0 +1,9 @@ +RestClient restClient = RestClient.create(); + +ResponseEntity response = restClient + .method(HttpMethod.POST) + .uri("http://mockbin.com/har") + .contentType(MediaType.TEXT_PLAIN) + .body("Hello World") + .retrieve() + .toEntity(String.class); 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) {}