diff --git a/README.md b/README.md index 38f211b..b8d378b 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,21 @@ -## OpenAPI Forge - C# +## OpenAPI Forge - Java This repository is the C# generator for the [OpenAPI Forge](https://github.com/ScottLogic/openapi-forge), see that repository for usage instructions: https://github.com/ScottLogic/openapi-forge +## Configuration + +OpenAPI Forge is opinionated in its approach, we don't provide a vast range of configuration options, just the essentials! You can list the generator-specific configuration options by running the `generate-options` command as follows: + +``` +% openapi-forge generator-options openapi-forge-java +This generator has a number of additional options which can be supplied when executing the 'forge' command. + +Options: + --generator.package The package for the generated classes. +``` + ## Development ### Running diff --git a/config.json b/config.json new file mode 100644 index 0000000..2955574 --- /dev/null +++ b/config.json @@ -0,0 +1,6 @@ +{ + "package": { + "default": "com.openapi.forge", + "description": "The package for the generated classes." + } +} diff --git a/formatter.js b/formatter.js index bdef50e..f004271 100644 --- a/formatter.js +++ b/formatter.js @@ -1,14 +1,19 @@ const prettier_cli = require("prettier/cli.js"); const logLevels = [ - /* quiet */ - "silent", - /* standard */ - "warn", - /* verbose */ - "debug", - ]; - - module.exports = (folder, logLevel) => { - return prettier_cli.run(["--write", folder, "--loglevel", logLevels[logLevel]]); - }; + /* quiet */ + "silent", + /* standard */ + "warn", + /* verbose */ + "debug", +]; + +module.exports = (folder, logLevel) => { + return prettier_cli.run([ + "--write", + folder, + "--loglevel", + logLevels[logLevel], + ]); +}; diff --git a/helpers/complexReturnType.js b/helpers/complexReturnType.js index 4c12136..f9b4cf4 100644 --- a/helpers/complexReturnType.js +++ b/helpers/complexReturnType.js @@ -1,19 +1,19 @@ const typeConvert = require("./typeConvert"); -const complexReturnType = (responseSchema) => { - const responseType = typeConvert(responseSchema); - switch (responseType) { - case "boolean": - case "int": - case "long": - case "float": - case "double": - case "String": - return false; - case "Date": - default: - return true; - } -} +const complexReturnType = (responseSchema) => { + const responseType = typeConvert(responseSchema); + switch (responseType) { + case "boolean": + case "int": + case "long": + case "float": + case "double": + case "String": + return false; + case "Date": + default: + return true; + } +}; -module.exports = complexReturnType; \ No newline at end of file +module.exports = complexReturnType; diff --git a/helpers/createHeaderParamsSnippet.js b/helpers/createHeaderParamsSnippet.js index acfb9a3..20a5e03 100644 --- a/helpers/createHeaderParamsSnippet.js +++ b/helpers/createHeaderParamsSnippet.js @@ -9,13 +9,13 @@ const createHeaderParamsSnippet = (sortedParams) => { let cookieParams = getParametersByType(sortedParams, "cookie"); if (cookieParams.length !== 0) { let safeParamName = toParamName(cookieParams[0].name); - headerSnippet += `request.newBuilder().addHeader("cookie", $"${cookieParams[0].name}={${safeParamName}}`; + headerSnippet += `.addHeader("cookie", "${cookieParams[0].name}={${safeParamName}}`; cookieParams = cookieParams.slice(1); for (const cookieParam of cookieParams) { safeParamName = toParamName(cookieParam.name); - headerSnippet += `;${cookieParam.name}={${safeParamName}}`; + headerSnippet += ` + ";${cookieParam.name}=" + ${safeParamName}`; } - headerSnippet += '");\n'; + headerSnippet += '")\n'; } const headerParams = getParametersByType(sortedParams, "header"); @@ -32,8 +32,8 @@ const createHeaderParamsSnippet = (sortedParams) => { switch (headerParam.schema.type) { case "array": headerSnippet += - `request.newBuilder().addHeader("${headerParam.name}", string.Join(",", ${safeParamName}))` + - ";\n"; + `.addHeader("${headerParam.name}", String.join(",", ${safeParamName}))`; + `.addHeader("${headerParam.name}", String.join(",", ${safeParamName}))`; break; case "object": { let serialisedObject = ""; @@ -43,14 +43,14 @@ const createHeaderParamsSnippet = (sortedParams) => { serialisedObject += `${propName},${safeParamName}.${propName}`; } headerSnippet += - `request.newBuilder().addHeader("${headerParam.name}", ${serialisedObject})` + - ";\n"; + `.addHeader("${headerParam.name}", ${serialisedObject})`; + `.addHeader("${headerParam.name}", ${serialisedObject})`; break; } default: headerSnippet += - `request.newBuilder().addHeader("${headerParam.name}", ${safeParamName})` + - ";\n"; + `.addHeader("${headerParam.name}", ${safeParamName})`; + `.addHeader("${headerParam.name}", ${safeParamName})`; } } diff --git a/helpers/createReturnStatement.js b/helpers/createReturnStatement.js index 3f1e2a0..6d8fbc6 100644 --- a/helpers/createReturnStatement.js +++ b/helpers/createReturnStatement.js @@ -8,7 +8,7 @@ const mapperListHandle = (responseType) => { } else { return `${responseType}.class`; } -} +}; const createReturnStatement = (responseSchema) => { const responseType = typeConvert(responseSchema); @@ -24,15 +24,19 @@ const createReturnStatement = (responseSchema) => { returnStatement = `${responseType}.Parse(responseBody)`; break; case "String": - returnStatement = "responseBody"; + returnStatement = "responseBodyString"; break; default: mapperStatements = `ObjectMapper deserMapper = new ObjectMapper();\n`; - mapperStatements += `${responseType} ${toParamName(responseType)} = deserMapper.readValue(responseBodyString, ${mapperListHandle(responseType)});\n` + mapperStatements += `${responseType} ${toParamName( + responseType + )} = deserMapper.readValue(responseBodyString, ${mapperListHandle( + responseType + )});\n`; returnStatement = toParamName(responseType); } - return new Handlebars.SafeString(mapperStatements + `return ${returnStatement};`); + return new Handlebars.SafeString(mapperStatements + `responseObject = ${returnStatement};`); }; module.exports = createReturnStatement; diff --git a/helpers/queryType.js b/helpers/queryType.js index d84d104..f4179b1 100644 --- a/helpers/queryType.js +++ b/helpers/queryType.js @@ -1,13 +1,13 @@ const queryType = (key) => { - if (key == 'delete') { - return '.delete()'; - } else if (key == "put") { - return '.put(RequestBody.create("", null))' - } else if (key == "post") { - return '.post(RequestBody.create("", null))' - } else { - return ''; - } + if (key == "delete") { + return ".delete()"; + } else if (key == "put") { + return '.put(RequestBody.create("", null))'; + } else if (key == "post") { + return '.post(RequestBody.create("", null))'; + } else { + return ""; + } }; module.exports = queryType; diff --git a/helpers/queryTypeWithBody.js b/helpers/queryTypeWithBody.js index 94d181b..ff98935 100644 --- a/helpers/queryTypeWithBody.js +++ b/helpers/queryTypeWithBody.js @@ -2,15 +2,15 @@ // This is why this function exists seperately. const queryTypeWithBody = (key) => { - if (key == "delete") { - return ".delete()"; - } else if (key == "put") { - return ".put(httpBody)" - } else if (key == "post") { - return ".post(httpBody)" - } else { - return ""; - } + if (key == "delete") { + return ".delete()"; + } else if (key == "put") { + return ".put(httpBody)"; + } else if (key == "post") { + return ".post(httpBody)"; + } else { + return ""; + } }; module.exports = queryTypeWithBody; diff --git a/helpers/safeTypeConvert.js b/helpers/safeTypeConvert.js index 04f94e9..673a0e3 100644 --- a/helpers/safeTypeConvert.js +++ b/helpers/safeTypeConvert.js @@ -1,6 +1,6 @@ const Handlebars = require("handlebars"); const typeConvert = require("./typeConvert"); -const safeTypeConvert = (prop) => new Handlebars.SafeString(typeConvert(prop)); +const safeTypeConvert = (prop, shouldBox = false) => new Handlebars.SafeString(typeConvert(prop, shouldBox)); module.exports = safeTypeConvert; diff --git a/helpers/toParamNameCapital.js b/helpers/toParamNameCapital.js index 6991b2d..c054816 100644 --- a/helpers/toParamNameCapital.js +++ b/helpers/toParamNameCapital.js @@ -1,7 +1,6 @@ const toParamNameCapital = (name) => { - name = name.replace(/[^a-z0-9_]/gi, ""); - return name.charAt(0).toUpperCase() + name.substr(1); - }; - - module.exports = toParamNameCapital; - \ No newline at end of file + name = name.replace(/[^a-z0-9_]/gi, ""); + return name.charAt(0).toUpperCase() + name.substr(1); +}; + +module.exports = toParamNameCapital; diff --git a/helpers/typeConvert.js b/helpers/typeConvert.js index 0143699..e3b412e 100644 --- a/helpers/typeConvert.js +++ b/helpers/typeConvert.js @@ -18,7 +18,7 @@ const fromFormat = (propFormat, shouldBox) => { case "string": return "String"; default: - return "void"; + return "Void"; } }; @@ -42,12 +42,12 @@ const fromType = (propType, additionalProperties, items, shouldBox) => { return "Object"; } default: - return "void"; + return shouldBox ? "Void" : "void"; } }; const typeConvert = (prop, shouldBox = false) => { - if (prop === null) return "void"; + if (prop === null) return shouldBox ? "Void" : "void"; if (prop === undefined) return "object"; diff --git a/package-lock.json b/package-lock.json index ed86640..5fd154c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,7 +18,8 @@ "path": "^0.12.7", "prettier": "^2.7.1", "prettier-plugin-java": "^2.0.0", - "semantic-release": "^19.0.5" + "semantic-release": "^19.0.5", + "shelljs": "^0.8.5" }, "engines": { "node": ">=16.0.0" @@ -2439,6 +2440,15 @@ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", "dev": true }, + "node_modules/interpret": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/into-stream": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/into-stream/-/into-stream-6.0.0.tgz", @@ -6314,6 +6324,18 @@ "util-deprecate": "~1.0.1" } }, + "node_modules/rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==", + "dev": true, + "dependencies": { + "resolve": "^1.1.6" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/redent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", @@ -6625,6 +6647,23 @@ "node": ">=8" } }, + "node_modules/shelljs": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz", + "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==", + "dev": true, + "dependencies": { + "glob": "^7.0.0", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" + }, + "bin": { + "shjs": "bin/shjs" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", @@ -9230,6 +9269,12 @@ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", "dev": true }, + "interpret": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", + "dev": true + }, "into-stream": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/into-stream/-/into-stream-6.0.0.tgz", @@ -11990,6 +12035,15 @@ "util-deprecate": "~1.0.1" } }, + "rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==", + "dev": true, + "requires": { + "resolve": "^1.1.6" + } + }, "redent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", @@ -12215,6 +12269,17 @@ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true }, + "shelljs": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz", + "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==", + "dev": true, + "requires": { + "glob": "^7.0.0", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" + } + }, "signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", diff --git a/package.json b/package.json index b50d804..eaa0b33 100644 --- a/package.json +++ b/package.json @@ -3,8 +3,8 @@ "version": "0.1.0", "description": "OpenAPI-Forge Java template", "apiTemplates": [ - "src/main/java/com/example/springboot/ApiClient.java.handlebars", - "src/main/java/com/example/springboot/IApiClient.java.handlebars" + "ApiClient.java.handlebars", + "IApiClient.java.handlebars" ], "main": "index.js", "scripts": { @@ -44,7 +44,8 @@ "path": "^0.12.7", "prettier": "^2.7.1", "prettier-plugin-java": "^2.0.0", - "semantic-release": "^19.0.5" + "semantic-release": "^19.0.5", + "shelljs": "^0.8.5" }, "engines": { "node": ">=16.0.0" diff --git a/postProcess.js b/postProcess.js new file mode 100644 index 0000000..85a841e --- /dev/null +++ b/postProcess.js @@ -0,0 +1,5 @@ +const move_files = require("./postProcessMoveFiles"); + +module.exports = (folder, _, options) => { + move_files(folder, options); +}; diff --git a/postProcessMoveFiles.js b/postProcessMoveFiles.js new file mode 100644 index 0000000..6748c17 --- /dev/null +++ b/postProcessMoveFiles.js @@ -0,0 +1,15 @@ +const shell = require("shelljs"); + +// To match the java package/folder conventions, the java files that are generated from handlebars templates +// should be moved to src/main/java/. +// The package folder name comes from the generator options and that can given as a command line argument +// by --generator.package + +module.exports = (folder, options) => { + var package_name = options["generator.package"]; + var package_folder = package_name.replaceAll(".", "/"); + java_path = "src/main/java/" + package_folder; + shell.cd(folder); + shell.mkdir("-p", java_path); + shell.mv("-f", "*.java", java_path); +}; diff --git a/template/src/main/java/com/example/springboot/ApiClient.java.handlebars b/template/ApiClient.java.handlebars similarity index 78% rename from template/src/main/java/com/example/springboot/ApiClient.java.handlebars rename to template/ApiClient.java.handlebars index 4c896d3..1dd6c65 100644 --- a/template/src/main/java/com/example/springboot/ApiClient.java.handlebars +++ b/template/ApiClient.java.handlebars @@ -1,4 +1,4 @@ -package com.example.springboot; +package {{_options.[generator.package]}}; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -43,7 +43,7 @@ public class ApiClient{{_tag.name}} implements IApiClient{{_tag.name}} { {{#each _sortedParameters}} // {{description}} {{/each}} - public {{safeTypeConvert _response.schema}} {{operationId}}( + public HttpResponse<{{safeTypeConvert _response.schema true}}> {{operationId}}( {{~#each _sortedParameters ~}} {{~safeTypeConvert schema}} {{toParamName name ~}} {{~#unless @last}}, {{/unless ~}} @@ -64,19 +64,26 @@ public class ApiClient{{_tag.name}} implements IApiClient{{_tag.name}} { {{else}} {{{queryType @key}}} {{/if}} + {{createHeaderParamsSnippet _sortedParameters}} .build(); - {{createHeaderParamsSnippet _sortedParameters}} Call call = _client.newCall(request); Response response = call.execute(); + {{safeTypeConvert _response.schema}} responseObject; {{#if _response.schema}} String responseBodyString = response.body().string(); {{createReturnStatement _response.schema}} {{else}} + responseObject = null; response.close(); {{/if}} + return new HttpResponse<{{safeTypeConvert _response.schema}}>( + responseObject, + response.code(), + response.headers() + ); } {{else}} - public {{#if _response.schema}}{{safeTypeConvert _response.schema}}{{/if}} {{operationId}}({{#each _sortedParameters}}{{safeTypeConvert schema}} {{name}}{{#if schema.default}} = {{{quoteIfString schema.default}}}{{/if}}{{#unless @last}}, {{/unless}}{{/each}}) throws UnsupportedOperationException + public {{#if _response.schema}}HttpResponse<{{safeTypeConvert _response.schema true}}>{{/if}} {{operationId}}({{#each _sortedParameters}}{{safeTypeConvert schema}} {{name}}{{#if schema.default}} = {{{quoteIfString schema.default}}}{{/if}}{{#unless @last}}, {{/unless}}{{/each}}) throws UnsupportedOperationException { throw new UnsupportedOperationException("Operation 'uploadFile' most likely does not support json encoded requests which are not supported by openapi forge."); } @@ -84,5 +91,3 @@ public class ApiClient{{_tag.name}} implements IApiClient{{_tag.name}} { {{/ifEquals}} {{/each}} {{/each}} - -} \ No newline at end of file diff --git a/template/src/main/java/com/example/springboot/ApiModel.java.handlebars b/template/ApiModel.java.handlebars similarity index 83% rename from template/src/main/java/com/example/springboot/ApiModel.java.handlebars rename to template/ApiModel.java.handlebars index 87af193..52b615d 100644 --- a/template/src/main/java/com/example/springboot/ApiModel.java.handlebars +++ b/template/ApiModel.java.handlebars @@ -1,4 +1,4 @@ -package com.example.springboot; +package {{_options.[generator.package]}}; import java.util.Date; import java.util.List; diff --git a/template/src/main/java/com/example/springboot/Application.java.handlebars b/template/Application.java.handlebars similarity index 93% rename from template/src/main/java/com/example/springboot/Application.java.handlebars rename to template/Application.java.handlebars index e3998d1..999dbdc 100644 --- a/template/src/main/java/com/example/springboot/Application.java.handlebars +++ b/template/Application.java.handlebars @@ -1,4 +1,4 @@ -package com.example.springboot; +package {{_options.[generator.package]}}; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; diff --git a/template/src/main/java/com/example/springboot/Configuration.java.handlebars b/template/Configuration.java.handlebars similarity index 96% rename from template/src/main/java/com/example/springboot/Configuration.java.handlebars rename to template/Configuration.java.handlebars index 71845cc..1f77d3b 100644 --- a/template/src/main/java/com/example/springboot/Configuration.java.handlebars +++ b/template/Configuration.java.handlebars @@ -1,4 +1,4 @@ -package com.example.springboot; +package {{_options.[generator.package]}}; import java.util.Arrays; import java.util.List; diff --git a/template/HttpResponse.java.handlebars b/template/HttpResponse.java.handlebars new file mode 100644 index 0000000..8671a43 --- /dev/null +++ b/template/HttpResponse.java.handlebars @@ -0,0 +1,32 @@ +package com.example.springboot; + +import okhttp3.Headers; + +// +// Represents an HTTP response. +// +public class HttpResponse { + + // + // Gets the typed response. + // + public final T data; + + // + // Gets the HTTP status code. + // + public final int statusCode; + + // + // Gets the returned HTTP headers. + // + public final Headers headers; + + public HttpResponse(T data, int statusCode, Headers headers) { + this.data = data; + this.statusCode = statusCode; + this.headers = headers; + } + + +} diff --git a/template/src/main/java/com/example/springboot/IApiClient.java.handlebars b/template/IApiClient.java.handlebars similarity index 91% rename from template/src/main/java/com/example/springboot/IApiClient.java.handlebars rename to template/IApiClient.java.handlebars index 4463e7c..252e5ec 100644 --- a/template/src/main/java/com/example/springboot/IApiClient.java.handlebars +++ b/template/IApiClient.java.handlebars @@ -1,4 +1,4 @@ -package com.example.springboot; +package {{_options.[generator.package]}}; import java.util.List; @@ -35,7 +35,7 @@ import java.util.HashMap; {{#each _sortedParameters}} // {{description}} {{/each}} - public {{safeTypeConvert _response.schema}} {{operationId}}( + public HttpResponse<{{safeTypeConvert _response.schema}}> {{operationId}}( {{~#each _sortedParameters ~}} {{~safeTypeConvert schema}} {{toParamName name ~}} {{~#unless @last}}, {{/unless ~}}