From ce49fe587fe6ab4dcc2275590f49fb0b09777710 Mon Sep 17 00:00:00 2001 From: Erica Kastner Date: Thu, 26 Mar 2020 04:02:44 -0500 Subject: [PATCH] Generator for JavaScript/Apollo Client (#5645) Co-authored-by: William Cheng --- README.md | 3 +- bin/javascript-apollo-petstore.sh | 32 + docs/generators/javascript-apollo.md | 247 ++++ .../JavascriptApolloClientCodegen.java | 1162 +++++++++++++++++ .../Javascript-Apollo/.babelrc.mustache | 33 + .../Javascript-Apollo/ApiClient.mustache | 299 +++++ .../Javascript-Apollo/README.mustache | 226 ++++ .../resources/Javascript-Apollo/api.mustache | 88 ++ .../Javascript-Apollo/api_doc.mustache | 112 ++ .../Javascript-Apollo/api_test.mustache | 44 + .../Javascript-Apollo/enumClass.mustache | 15 + .../Javascript-Apollo/git_push.sh.mustache | 58 + .../Javascript-Apollo/gitignore.mustache | 33 + .../Javascript-Apollo/index.mustache | 58 + .../Javascript-Apollo/licenseInfo.mustache | 12 + .../resources/Javascript-Apollo/mocha.opts | 1 + .../Javascript-Apollo/model.mustache | 4 + .../Javascript-Apollo/model_doc.mustache | 26 + .../Javascript-Apollo/model_test.mustache | 65 + .../Javascript-Apollo/package.mustache | 29 + .../partial_model_enum_class.mustache | 24 + .../partial_model_generic.mustache | 82 ++ .../partial_model_inner_enum.mustache | 15 + .../resources/Javascript-Apollo/travis.yml | 5 + .../org.openapitools.codegen.CodegenConfig | 1 + pom.xml | 1 + website/i18n/en.json | 4 + 27 files changed, 2678 insertions(+), 1 deletion(-) create mode 100755 bin/javascript-apollo-petstore.sh create mode 100644 docs/generators/javascript-apollo.md create mode 100644 modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JavascriptApolloClientCodegen.java create mode 100644 modules/openapi-generator/src/main/resources/Javascript-Apollo/.babelrc.mustache create mode 100644 modules/openapi-generator/src/main/resources/Javascript-Apollo/ApiClient.mustache create mode 100644 modules/openapi-generator/src/main/resources/Javascript-Apollo/README.mustache create mode 100644 modules/openapi-generator/src/main/resources/Javascript-Apollo/api.mustache create mode 100644 modules/openapi-generator/src/main/resources/Javascript-Apollo/api_doc.mustache create mode 100644 modules/openapi-generator/src/main/resources/Javascript-Apollo/api_test.mustache create mode 100644 modules/openapi-generator/src/main/resources/Javascript-Apollo/enumClass.mustache create mode 100644 modules/openapi-generator/src/main/resources/Javascript-Apollo/git_push.sh.mustache create mode 100644 modules/openapi-generator/src/main/resources/Javascript-Apollo/gitignore.mustache create mode 100644 modules/openapi-generator/src/main/resources/Javascript-Apollo/index.mustache create mode 100644 modules/openapi-generator/src/main/resources/Javascript-Apollo/licenseInfo.mustache create mode 100644 modules/openapi-generator/src/main/resources/Javascript-Apollo/mocha.opts create mode 100644 modules/openapi-generator/src/main/resources/Javascript-Apollo/model.mustache create mode 100644 modules/openapi-generator/src/main/resources/Javascript-Apollo/model_doc.mustache create mode 100644 modules/openapi-generator/src/main/resources/Javascript-Apollo/model_test.mustache create mode 100644 modules/openapi-generator/src/main/resources/Javascript-Apollo/package.mustache create mode 100644 modules/openapi-generator/src/main/resources/Javascript-Apollo/partial_model_enum_class.mustache create mode 100644 modules/openapi-generator/src/main/resources/Javascript-Apollo/partial_model_generic.mustache create mode 100644 modules/openapi-generator/src/main/resources/Javascript-Apollo/partial_model_inner_enum.mustache create mode 100644 modules/openapi-generator/src/main/resources/Javascript-Apollo/travis.yml diff --git a/README.md b/README.md index 97b5ddc4095..f692cef84a6 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ OpenAPI Generator allows generation of API client libraries (SDK generation), se | | Languages/Frameworks | | -------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **API clients** | **ActionScript**, **Ada**, **Apex**, **Bash**, **C**, **C#** (.net 2.0, 3.5 or later, .NET Standard 1.3 - 2.0, .NET Core 2.0), **C++** (cpp-restsdk, Qt5, Tizen), **Clojure**, **Dart (1.x, 2.x)**, **Elixir**, **Elm**, **Eiffel**, **Erlang**, **Go**, **Groovy**, **Haskell** (http-client, Servant), **Java** (Jersey1.x, Jersey2.x, OkHttp, Retrofit1.x, Retrofit2.x, Feign, RestTemplate, RESTEasy, Vertx, Google API Client Library for Java, Rest-assured, Spring 5 Web Client, MicroProfile Rest Client), **k6**, **Kotlin**, **Lua**, **Nim**, **Node.js/JavaScript** (ES5, ES6, AngularJS with Google Closure Compiler annotations, Flow types), **Objective-C**, **OCaml**, **Perl**, **PHP**, **PowerShell**, **Python**, **R**, **Ruby**, **Rust** (rust, rust-server), **Scala** (akka, http4s, scalaz, sttp, swagger-async-httpclient), **Swift** (2.x, 3.x, 4.x, 5.x), **Typescript** (AngularJS, Angular (2.x - 8.x), Aurelia, Axios, Fetch, Inversify, jQuery, Node, Rxjs) | +| **API clients** | **ActionScript**, **Ada**, **Apex**, **Bash**, **C**, **C#** (.net 2.0, 3.5 or later, .NET Standard 1.3 - 2.0, .NET Core 2.0), **C++** (cpp-restsdk, Qt5, Tizen), **Clojure**, **Dart (1.x, 2.x)**, **Elixir**, **Elm**, **Eiffel**, **Erlang**, **Go**, **Groovy**, **Haskell** (http-client, Servant), **Java** (Jersey1.x, Jersey2.x, OkHttp, Retrofit1.x, Retrofit2.x, Feign, RestTemplate, RESTEasy, Vertx, Google API Client Library for Java, Rest-assured, Spring 5 Web Client, MicroProfile Rest Client), **k6**, **Kotlin**, **Lua**, **Nim**, **Node.js/JavaScript** (ES5, ES6, AngularJS with Google Closure Compiler annotations, Flow types, Apollo GraphQL DataStore), **Objective-C**, **OCaml**, **Perl**, **PHP**, **PowerShell**, **Python**, **R**, **Ruby**, **Rust** (rust, rust-server), **Scala** (akka, http4s, scalaz, sttp, swagger-async-httpclient), **Swift** (2.x, 3.x, 4.x, 5.x), **Typescript** (AngularJS, Angular (2.x - 8.x), Aurelia, Axios, Fetch, Inversify, jQuery, Node, Rxjs) | | **Server stubs** | **Ada**, **C#** (ASP.NET Core, NancyFx), **C++** (Pistache, Restbed, Qt5 QHTTPEngine), **Erlang**, **F#** (Giraffe), **Go** (net/http, Gin), **Haskell** (Servant), **Java** (MSF4J, Spring, Undertow, JAX-RS: CDI, CXF, Inflector, Jersey, RestEasy, Play Framework, [PKMST](https://github.com/ProKarma-Inc/pkmst-getting-started-examples), [Vert.x](https://vertx.io/)), **Kotlin** (Spring Boot, Ktor, Vertx), **PHP** (Laravel, Lumen, Slim, Silex, [Symfony](https://symfony.com/), [Zend Expressive](https://github.com/zendframework/zend-expressive)), **Python** (Flask), **NodeJS**, **Ruby** (Sinatra, Rails5), **Rust** (rust-server), **Scala** ([Finch](https://github.com/finagle/finch), [Lagom](https://github.com/lagom/lagom), [Play](https://www.playframework.com/), Scalatra) | | **API documentation generators** | **HTML**, **Confluence Wiki**, **Asciidoc** | | **Configuration files** | [**Apache2**](https://httpd.apache.org/) | @@ -803,6 +803,7 @@ Here is a list of template creators: * Java (Rest-assured): @viclovsky * Java (Java 11 Native HTTP client): @bbdouglas * Javascript/NodeJS: @jfiala + * Javascript (Apollo DataSource): @erithmetic * Javascript (Closure-annotated Angular) @achew22 * Javascript (Flow types) @jaypea * JMeter: @davidkiss diff --git a/bin/javascript-apollo-petstore.sh b/bin/javascript-apollo-petstore.sh new file mode 100755 index 00000000000..13a20dea570 --- /dev/null +++ b/bin/javascript-apollo-petstore.sh @@ -0,0 +1,32 @@ +#!/bin/sh + +SCRIPT="$0" +echo "# START SCRIPT: $SCRIPT" + +while [ -h "$SCRIPT" ] ; do + ls=`ls -ld "$SCRIPT"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + SCRIPT="$link" + else + SCRIPT=`dirname "$SCRIPT"`/"$link" + fi +done + +if [ ! -d "${APP_DIR}" ]; then + APP_DIR=`dirname "$SCRIPT"`/.. + APP_DIR=`cd "${APP_DIR}"; pwd` +fi + +executable="./modules/openapi-generator-cli/target/openapi-generator-cli.jar" + +if [ ! -f "$executable" ] +then + mvn -B clean package +fi + +# if you've executed sbt assembly previously it will use that instead. +export JAVA_OPTS="${JAVA_OPTS} -Xmx1024M -DloggerPath=conf/log4j.properties" +ags="generate -t modules/openapi-generator/src/main/resources/Javascript-Apollo -i modules/openapi-generator/src/test/resources/2_0/petstore.yaml -g javascript-apollo -o samples/client/petstore/javascript-apollo --additional-properties appName=PetstoreClient $@" + +java $JAVA_OPTS -jar $executable $ags diff --git a/docs/generators/javascript-apollo.md b/docs/generators/javascript-apollo.md new file mode 100644 index 00000000000..3d012ca6441 --- /dev/null +++ b/docs/generators/javascript-apollo.md @@ -0,0 +1,247 @@ +--- +title: Config Options for javascript-apollo +sidebar_label: javascript-apollo +--- + +| Option | Description | Values | Default | +| ------ | ----------- | ------ | ------- | +|allowUnicodeIdentifiers|boolean, toggles whether unicode identifiers are allowed in names or not, default is false| |false| +|ensureUniqueParams|Whether to ensure parameter names are unique in an operation (rename parameters that are not).| |true| +|enumNameSuffix|Suffix that will be appended to all enum names. A special 'v4-compat' value enables the backward-compatible behavior (as pre v4.2.3)| |v4-compat| +|enumPropertyNaming|Naming convention for enum properties: 'camelCase', 'PascalCase', 'snake_case', 'UPPERCASE', and 'original'| |PascalCase| +|modelPropertyNaming|Naming convention for the property: 'camelCase', 'PascalCase', 'snake_case' and 'original', which keeps the original name. Only change it if you provide your own run-time code for (de-)serialization of models| |original| +|npmName|The name under which you want to publish generated npm package. Required to generate a full package| |null| +|npmRepository|Use this property to set an url your private npmRepo in the package.json| |null| +|npmVersion|The version of your npm package. If not provided, using the version from the OpenAPI specification file.| |1.0.0| +|nullSafeAdditionalProps|Set to make additional properties types declare that their indexer may return undefined| |false| +|prependFormOrBodyParameters|Add form or body parameters to the beginning of the parameter list.| |false| +|snapshot|When setting this property to true, the version will be suffixed with -SNAPSHOT.yyyyMMddHHmm| |false| +|sortModelPropertiesByRequiredFlag|Sort model properties to place required parameters before optional parameters.| |true| +|sortParamsByRequiredFlag|Sort method arguments to place required parameters before optional parameters.| |true| + +## IMPORT MAPPING + +| Type/Alias | Imports | +| ---------- | ------- | + + +## INSTANTIATION TYPES + +| Type/Alias | Instantiated By | +| ---------- | --------------- | +|array|Array| +|list|Array| +|map|Object| + + +## LANGUAGE PRIMITIVES + + + +## RESERVED WORDS + + + +## FEATURE SET + + +### Client Modification Feature +| Name | Supported | Defined By | +| ---- | --------- | ---------- | +|BasePath|✓|ToolingExtension +|Authorizations|✗|ToolingExtension +|UserAgent|✗|ToolingExtension + +### Data Type Feature +| Name | Supported | Defined By | +| ---- | --------- | ---------- | +|Custom|✗|OAS2,OAS3 +|Int32|✓|OAS2,OAS3 +|Int64|✓|OAS2,OAS3 +|Float|✓|OAS2,OAS3 +|Double|✓|OAS2,OAS3 +|Decimal|✓|ToolingExtension +|String|✓|OAS2,OAS3 +|Byte|✓|OAS2,OAS3 +|Binary|✓|OAS2,OAS3 +|Boolean|✓|OAS2,OAS3 +|Date|✓|OAS2,OAS3 +|DateTime|✓|OAS2,OAS3 +|Password|✓|OAS2,OAS3 +|File|✓|OAS2 +|Array|✓|OAS2,OAS3 +|Maps|✓|ToolingExtension +|CollectionFormat|✓|OAS2 +|CollectionFormatMulti|✓|OAS2 +|Enum|✓|OAS2,OAS3 +|ArrayOfEnum|✓|ToolingExtension +|ArrayOfModel|✓|ToolingExtension +|ArrayOfCollectionOfPrimitives|✓|ToolingExtension +|ArrayOfCollectionOfModel|✓|ToolingExtension +|ArrayOfCollectionOfEnum|✓|ToolingExtension +|MapOfEnum|✓|ToolingExtension +|MapOfModel|✓|ToolingExtension +|MapOfCollectionOfPrimitives|✓|ToolingExtension +|MapOfCollectionOfModel|✓|ToolingExtension +|MapOfCollectionOfEnum|✓|ToolingExtension + +### Documentation Feature +| Name | Supported | Defined By | +| ---- | --------- | ---------- | +|Readme|✓|ToolingExtension +|Model|✓|ToolingExtension +|Api|✓|ToolingExtension + +### Global Feature +| Name | Supported | Defined By | +| ---- | --------- | ---------- | +|Host|✓|OAS2,OAS3 +|BasePath|✓|OAS2,OAS3 +|Info|✓|OAS2,OAS3 +|Schemes|✗|OAS2,OAS3 +|PartialSchemes|✓|OAS2,OAS3 +|Consumes|✓|OAS2 +|Produces|✓|OAS2 +|ExternalDocumentation|✓|OAS2,OAS3 +|Examples|✓|OAS2,OAS3 +|XMLStructureDefinitions|✗|OAS2,OAS3 +|MultiServer|✗|OAS3 +|ParameterizedServer|✗|OAS3 +|ParameterStyling|✗|OAS3 +|Callbacks|✗|OAS3 +|LinkObjects|✗|OAS3 + +### Parameter Feature +| Name | Supported | Defined By | +| ---- | --------- | ---------- | +|Path|✓|OAS2,OAS3 +|Query|✓|OAS2,OAS3 +|Header|✓|OAS2,OAS3 +|Body|✓|OAS2 +|FormUnencoded|✓|OAS2 +|FormMultipart|✓|OAS2 +|Cookie|✓|OAS3 + +### Schema Support Feature +| Name | Supported | Defined By | +| ---- | --------- | ---------- | +|Simple|✓|OAS2,OAS3 +|Composite|✓|OAS2,OAS3 +|Polymorphism|✓|OAS2,OAS3 +|Union|✗|OAS3 + +### Security Feature +| Name | Supported | Defined By | +| ---- | --------- | ---------- | +|BasicAuth|✗|OAS2,OAS3 +|ApiKey|✗|OAS2,OAS3 +|OpenIDConnect|✗|OAS3 +|BearerToken|✗|OAS3 +|OAuth2_Implicit|✗|OAS2,OAS3 +|OAuth2_Password|✗|OAS2,OAS3 +|OAuth2_ClientCredentials|✗|OAS2,OAS3 +|OAuth2_AuthorizationCode|✗|OAS2,OAS3 + +### Wire Format Feature +| Name | Supported | Defined By | +| ---- | --------- | ---------- | +|JSON|✓|OAS2,OAS3 +|XML|✓|OAS2,OAS3 +|PROTOBUF|✗|ToolingExtension +|Custom|✗|OAS2,OAS3 diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JavascriptApolloClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JavascriptApolloClientCodegen.java new file mode 100644 index 00000000000..c02bb74c73c --- /dev/null +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JavascriptApolloClientCodegen.java @@ -0,0 +1,1162 @@ +/* + * Copyright 2018 OpenAPI-Generator Contributors (https://openapi-generator.tech) + * Copyright 2018 SmartBear Software + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.openapitools.codegen.languages; + +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.info.License; +import io.swagger.v3.oas.models.media.ArraySchema; +import io.swagger.v3.oas.models.media.Schema; +import org.apache.commons.io.FilenameUtils; +import org.apache.commons.lang3.StringUtils; +import org.openapitools.codegen.*; +import org.openapitools.codegen.meta.features.DocumentationFeature; +import org.openapitools.codegen.utils.ModelUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.util.*; + +import static org.openapitools.codegen.utils.OnceLogger.once; +import static org.openapitools.codegen.utils.StringUtils.*; + +public class JavascriptApolloClientCodegen extends DefaultCodegen implements CodegenConfig { + @SuppressWarnings("hiding") + private static final Logger LOGGER = LoggerFactory.getLogger(JavascriptApolloClientCodegen.class); + + public static final String PROJECT_NAME = "projectName"; + public static final String MODULE_NAME = "moduleName"; + public static final String PROJECT_DESCRIPTION = "projectDescription"; + public static final String PROJECT_VERSION = "projectVersion"; + public static final String USE_INHERITANCE = "useInheritance"; + public static final String EMIT_JS_DOC = "emitJSDoc"; + public static final String NPM_REPOSITORY = "npmRepository"; + + final String[][] JAVASCRIPT_SUPPORTING_FILES = new String[][]{ + new String[]{"package.mustache", "package.json"}, + // new String[]{"index.mustache", "src/index.js", }, + // new String[]{"ApiClient.mustache", "src/ApiClient.js"}, + new String[]{"git_push.sh.mustache", "git_push.sh"}, + new String[]{"README.mustache", "README.md"}, + new String[]{"mocha.opts", "mocha.opts"}, + new String[]{"travis.yml", ".travis.yml"} + }; + + final String[][] JAVASCRIPT_ES6_SUPPORTING_FILES = new String[][]{ + new String[]{"package.mustache", "package.json"}, + // new String[]{"index.mustache", "src/index.js"}, + // new String[]{"ApiClient.mustache", "src/ApiClient.js"}, + new String[]{"git_push.sh.mustache", "git_push.sh"}, + new String[]{"README.mustache", "README.md"}, + new String[]{"mocha.opts", "mocha.opts"}, + new String[]{"travis.yml", ".travis.yml"}, + new String[]{".babelrc.mustache", ".babelrc"} + }; + + protected String projectName; + protected String moduleName; + protected String projectDescription; + protected String projectVersion; + protected String licenseName; + + protected String invokerPackage; + protected String sourceFolder = "src"; + protected boolean emitJSDoc = true; + protected String apiDocPath = "docs/"; + protected String modelDocPath = "docs/"; + protected String apiTestPath = "api/"; + protected String modelTestPath = "model/"; + protected String npmRepository = null; + private String modelPropertyNaming = "camelCase"; + + public JavascriptApolloClientCodegen() { + super(); + + modifyFeatureSet(features -> features.includeDocumentationFeatures(DocumentationFeature.Readme)); + + outputFolder = "generated-code/js"; + modelTemplateFiles.put("model.mustache", ".js"); + modelTestTemplateFiles.put("model_test.mustache", ".js"); + apiTemplateFiles.put("api.mustache", ".js"); + apiTestTemplateFiles.put("api_test.mustache", ".js"); + // subfolder Javascript/es6 + embeddedTemplateDir = templateDir = "Javascript-Apollo"; + apiPackage = "api"; + modelPackage = "model"; + modelDocTemplateFiles.put("model_doc.mustache", ".md"); + apiDocTemplateFiles.put("api_doc.mustache", ".md"); + + // default HIDE_GENERATION_TIMESTAMP to true + hideGenerationTimestamp = Boolean.TRUE; + + // reference: http://www.w3schools.com/js/js_reserved.asp + setReservedWordsLowerCase( + Arrays.asList( + "abstract", "arguments", "boolean", "break", "byte", + "case", "catch", "char", "class", "const", + "continue", "debugger", "default", "delete", "do", + "double", "else", "enum", "eval", "export", + "extends", "false", "final", "finally", "float", + "for", "function", "goto", "if", "implements", + "import", "in", "instanceof", "int", "interface", + "let", "long", "native", "new", "null", + "package", "private", "protected", "public", "return", + "short", "static", "super", "switch", "synchronized", + "this", "throw", "throws", "transient", "true", + "try", "typeof", "var", "void", "volatile", + "while", "with", "yield", + "Array", "Date", "eval", "function", "hasOwnProperty", + "Infinity", "isFinite", "isNaN", "isPrototypeOf", + "Math", "NaN", "Number", "Object", + "prototype", "String", "toString", "undefined", "valueOf") + ); + + languageSpecificPrimitives = new HashSet( + Arrays.asList("String", "Boolean", "Number", "Array", "Object", "Date", "File", "Blob") + ); + defaultIncludes = new HashSet(languageSpecificPrimitives); + + instantiationTypes.put("array", "Array"); + instantiationTypes.put("list", "Array"); + instantiationTypes.put("map", "Object"); + typeMapping.clear(); + typeMapping.put("array", "Array"); + typeMapping.put("map", "Object"); + typeMapping.put("List", "Array"); + typeMapping.put("boolean", "Boolean"); + typeMapping.put("string", "String"); + typeMapping.put("int", "Number"); + typeMapping.put("float", "Number"); + typeMapping.put("number", "Number"); + typeMapping.put("BigDecimal", "Number"); + typeMapping.put("DateTime", "Date"); + typeMapping.put("date", "Date"); + typeMapping.put("long", "Number"); + typeMapping.put("short", "Number"); + typeMapping.put("char", "String"); + typeMapping.put("double", "Number"); + typeMapping.put("object", "Object"); + typeMapping.put("integer", "Number"); + typeMapping.put("ByteArray", "Blob"); + typeMapping.put("binary", "File"); + typeMapping.put("file", "File"); + typeMapping.put("UUID", "String"); + typeMapping.put("URI", "String"); + + importMapping.clear(); + + cliOptions.add(new CliOption(CodegenConstants.SOURCE_FOLDER, CodegenConstants.SOURCE_FOLDER_DESC).defaultValue("src")); + cliOptions.add(new CliOption(CodegenConstants.INVOKER_PACKAGE, CodegenConstants.INVOKER_PACKAGE_DESC)); + cliOptions.add(new CliOption(CodegenConstants.API_PACKAGE, CodegenConstants.API_PACKAGE_DESC)); + cliOptions.add(new CliOption(CodegenConstants.MODEL_PACKAGE, CodegenConstants.MODEL_PACKAGE_DESC)); + cliOptions.add(new CliOption(PROJECT_NAME, + "name of the project (Default: generated from info.title or \"openapi-js-client\")")); + cliOptions.add(new CliOption(MODULE_NAME, + "module name for AMD, Node or globals (Default: generated from )")); + cliOptions.add(new CliOption(PROJECT_DESCRIPTION, + "description of the project (Default: using info.description or \"Client library of \")")); + cliOptions.add(new CliOption(PROJECT_VERSION, + "version of the project (Default: using info.version or \"1.0.0\")")); + cliOptions.add(new CliOption(CodegenConstants.LICENSE_NAME, + "name of the license the project uses (Default: using info.license.name)")); + cliOptions.add(new CliOption(EMIT_JS_DOC, + "generate JSDoc comments") + .defaultValue(Boolean.TRUE.toString())); + cliOptions.add(new CliOption(USE_INHERITANCE, + "use JavaScript prototype chains & delegation for inheritance") + .defaultValue(Boolean.TRUE.toString())); + cliOptions.add(new CliOption(CodegenConstants.HIDE_GENERATION_TIMESTAMP, CodegenConstants.HIDE_GENERATION_TIMESTAMP_DESC) + .defaultValue(Boolean.TRUE.toString())); + cliOptions.add(new CliOption(CodegenConstants.MODEL_PROPERTY_NAMING, CodegenConstants.MODEL_PROPERTY_NAMING_DESC).defaultValue("camelCase")); + cliOptions.add(new CliOption(NPM_REPOSITORY, "Use this property to set an url your private npmRepo in the package.json")); + } + + @Override + public CodegenType getTag() { + return CodegenType.CLIENT; + } + + @Override + public String getName() { + return "javascript-apollo"; + } + + @Override + public String getHelp() { + return "Generates a JavaScript client library using Apollo RESTDatasource."; + } + + @Override + public void processOpts() { + super.processOpts(); + + if (StringUtils.isEmpty(System.getenv("JS_POST_PROCESS_FILE"))) { + LOGGER.info("Environment variable JS_POST_PROCESS_FILE not defined so the JS code may not be properly formatted. To define it, try 'export JS_POST_PROCESS_FILE=\"/usr/local/bin/js-beautify -r -f\"' (Linux/Mac)"); + LOGGER.info("NOTE: To enable file post-processing, 'enablePostProcessFile' must be set to `true` (--enable-post-process-file for CLI)."); + } + + if (additionalProperties.containsKey(PROJECT_NAME)) { + setProjectName(((String) additionalProperties.get(PROJECT_NAME))); + } + if (additionalProperties.containsKey(MODULE_NAME)) { + setModuleName(((String) additionalProperties.get(MODULE_NAME))); + } + if (additionalProperties.containsKey(PROJECT_DESCRIPTION)) { + setProjectDescription(((String) additionalProperties.get(PROJECT_DESCRIPTION))); + } + if (additionalProperties.containsKey(PROJECT_VERSION)) { + setProjectVersion(((String) additionalProperties.get(PROJECT_VERSION))); + } + if (additionalProperties.containsKey(CodegenConstants.LICENSE_NAME)) { + setLicenseName(((String) additionalProperties.get(CodegenConstants.LICENSE_NAME))); + } + if (additionalProperties.containsKey(CodegenConstants.SOURCE_FOLDER)) { + setSourceFolder((String) additionalProperties.get(CodegenConstants.SOURCE_FOLDER)); + } + if (additionalProperties.containsKey(CodegenConstants.INVOKER_PACKAGE)) { + setInvokerPackage((String) additionalProperties.get(CodegenConstants.INVOKER_PACKAGE)); + } + if (additionalProperties.containsKey(USE_INHERITANCE)) { + setUseInheritance(convertPropertyToBooleanAndWriteBack(USE_INHERITANCE)); + } else { + supportsInheritance = true; + supportsMixins = true; + } + if (additionalProperties.containsKey(EMIT_JS_DOC)) { + setEmitJSDoc(convertPropertyToBooleanAndWriteBack(EMIT_JS_DOC)); + } + if (additionalProperties.containsKey(CodegenConstants.MODEL_PROPERTY_NAMING)) { + setModelPropertyNaming((String) additionalProperties.get(CodegenConstants.MODEL_PROPERTY_NAMING)); + } + if (additionalProperties.containsKey(NPM_REPOSITORY)) { + setNpmRepository(((String) additionalProperties.get(NPM_REPOSITORY))); + } + } + + @Override + public void preprocessOpenAPI(OpenAPI openAPI) { + super.preprocessOpenAPI(openAPI); + + if (openAPI.getInfo() != null) { + Info info = openAPI.getInfo(); + if (StringUtils.isBlank(projectName) && info.getTitle() != null) { + // when projectName is not specified, generate it from info.title + projectName = sanitizeName(dashize(info.getTitle())); + } + if (StringUtils.isBlank(projectVersion)) { + // when projectVersion is not specified, use info.version + projectVersion = escapeUnsafeCharacters(escapeQuotationMark(info.getVersion())); + } + if (projectDescription == null) { + // when projectDescription is not specified, use info.description + if (StringUtils.isEmpty(info.getDescription())) { + projectDescription = "JS API client generated by OpenAPI Generator"; + } else { + projectDescription = sanitizeName(info.getDescription()); + } + } + + // when licenceName is not specified, use info.license + if (additionalProperties.get(CodegenConstants.LICENSE_NAME) == null && info.getLicense() != null) { + License license = info.getLicense(); + licenseName = license.getName(); + } + } + + // default values + if (StringUtils.isBlank(projectName)) { + projectName = "openapi-js-client"; + } + if (StringUtils.isBlank(moduleName)) { + moduleName = camelize(underscore(projectName)); + } + if (StringUtils.isBlank(projectVersion)) { + projectVersion = "1.0.0"; + } + if (projectDescription == null) { + projectDescription = "Client library of " + projectName; + } + if (StringUtils.isBlank(licenseName)) { + licenseName = "Unlicense"; + } + + additionalProperties.put(PROJECT_NAME, projectName); + additionalProperties.put(MODULE_NAME, moduleName); + additionalProperties.put(PROJECT_DESCRIPTION, escapeText(projectDescription)); + additionalProperties.put(PROJECT_VERSION, projectVersion); + additionalProperties.put(CodegenConstants.LICENSE_NAME, licenseName); + additionalProperties.put(CodegenConstants.API_PACKAGE, apiPackage); + additionalProperties.put(CodegenConstants.INVOKER_PACKAGE, invokerPackage); + additionalProperties.put(CodegenConstants.MODEL_PACKAGE, modelPackage); + additionalProperties.put(CodegenConstants.SOURCE_FOLDER, sourceFolder); + additionalProperties.put(USE_INHERITANCE, supportsInheritance); + additionalProperties.put(EMIT_JS_DOC, emitJSDoc); + additionalProperties.put(NPM_REPOSITORY, npmRepository); + + // make api and model doc path available in mustache template + additionalProperties.put("apiDocPath", apiDocPath); + additionalProperties.put("modelDocPath", modelDocPath); + + String[][] supportingTemplateFiles = JAVASCRIPT_SUPPORTING_FILES; + + for (String[] supportingTemplateFile : supportingTemplateFiles) { + supportingFiles.add(new SupportingFile(supportingTemplateFile[0], "", supportingTemplateFile[1])); + } + + supportingFiles.add(new SupportingFile("index.mustache", createPath(sourceFolder, invokerPackage), "index.js")); + supportingFiles.add(new SupportingFile("ApiClient.mustache", createPath(sourceFolder, invokerPackage), "ApiClient.js")); + + } + + @Override + public String escapeReservedWord(String name) { + if (this.reservedWordsMappings().containsKey(name)) { + return this.reservedWordsMappings().get(name); + } + return "_" + name; + } + + /** + * Concatenates an array of path segments into a path string. + * + * @param segments The path segments to concatenate. A segment may contain either of the file separator characters '\' or '/'. + * A segment is ignored if it is null, empty or ".". + * @return A path string using the correct platform-specific file separator character. + */ + private String createPath(String... segments) { + StringBuilder buf = new StringBuilder(); + for (String segment : segments) { + if (!StringUtils.isEmpty(segment) && !segment.equals(".")) { + if (buf.length() != 0) + buf.append(File.separatorChar); + buf.append(segment); + } + } + for (int i = 0; i < buf.length(); i++) { + char c = buf.charAt(i); + if ((c == '/' || c == '\\') && c != File.separatorChar) + buf.setCharAt(i, File.separatorChar); + } + return buf.toString(); + } + + @Override + public String apiTestFileFolder() { + return createPath(outputFolder, "test", invokerPackage, apiTestPath); + } + + @Override + public String modelTestFileFolder() { + return createPath(outputFolder, "test", invokerPackage, modelTestPath); + } + + @Override + public String apiFileFolder() { + return createPath(outputFolder, sourceFolder, invokerPackage, apiPackage()); + } + + @Override + public String modelFileFolder() { + return createPath(outputFolder, sourceFolder, invokerPackage, modelPackage()); + } + + public String getInvokerPackage() { + return invokerPackage; + } + + public void setInvokerPackage(String invokerPackage) { + this.invokerPackage = invokerPackage; + } + + public void setSourceFolder(String sourceFolder) { + this.sourceFolder = sourceFolder; + } + + public void setProjectName(String projectName) { + this.projectName = projectName; + } + + public void setModuleName(String moduleName) { + this.moduleName = moduleName; + } + + public void setProjectDescription(String projectDescription) { + this.projectDescription = projectDescription; + } + + public void setProjectVersion(String projectVersion) { + this.projectVersion = projectVersion; + } + + public void setLicenseName(String licenseName) { + this.licenseName = licenseName; + } + + public void setNpmRepository(String npmRepository) { + this.npmRepository = npmRepository; + } + + public void setUseInheritance(boolean useInheritance) { + this.supportsInheritance = useInheritance; + this.supportsMixins = useInheritance; + } + + public void setEmitJSDoc(boolean emitJSDoc) { + this.emitJSDoc = emitJSDoc; + } + + @Override + public String apiDocFileFolder() { + return createPath(outputFolder, apiDocPath); + } + + @Override + public String modelDocFileFolder() { + return createPath(outputFolder, modelDocPath); + } + + @Override + public String toApiDocFilename(String name) { + return toApiName(name); + } + + @Override + public String toModelDocFilename(String name) { + return toModelName(name); + } + + @Override + public String toApiTestFilename(String name) { + return toApiName(name) + ".spec"; + } + + @Override + public String toModelTestFilename(String name) { + return toModelName(name) + ".spec"; + } + + public String getModelPropertyNaming() { + return this.modelPropertyNaming; + } + + private String getNameUsingModelPropertyNaming(String name) { + switch (CodegenConstants.MODEL_PROPERTY_NAMING_TYPE.valueOf(getModelPropertyNaming())) { + case original: + return name; + case camelCase: + return camelize(name, true); + case PascalCase: + return camelize(name); + case snake_case: + return underscore(name); + default: + throw new IllegalArgumentException("Invalid model property naming '" + + name + "'. Must be 'original', 'camelCase', " + + "'PascalCase' or 'snake_case'"); + } + } + + @Override + public String toVarName(String name) { + // sanitize name + name = sanitizeName(name); // FIXME parameter should not be assigned. Also declare it as "final" + + if ("_".equals(name)) { + name = "_u"; + } + + // if it's all uppper case, do nothing + if (name.matches("^[A-Z_]*$")) { + return name; + } + + // camelize (lower first character) the variable name + // pet_id => petId + name = getNameUsingModelPropertyNaming(name); + + // for reserved word or word starting with number, append _ + if (isReservedWord(name) || name.matches("^\\d.*")) { + name = escapeReservedWord(name); + } + + return name; + } + + @Override + public String toParamName(String name) { + // should be the same as variable name + return toVarName(name); + } + + @Override + public String toModelName(String name) { + name = sanitizeName(name); // FIXME parameter should not be assigned. Also declare it as "final" + + if (!StringUtils.isEmpty(modelNamePrefix)) { + name = modelNamePrefix + "_" + name; + } + + if (!StringUtils.isEmpty(modelNameSuffix)) { + name = name + "_" + modelNameSuffix; + } + + // camelize the model name + // phone_number => PhoneNumber + name = camelize(name); + + // model name cannot use reserved keyword, e.g. return + if (isReservedWord(name)) { + String modelName = "Model" + name; + LOGGER.warn(name + " (reserved word) cannot be used as model name. Renamed to " + modelName); + return modelName; + } + + // model name starts with number + if (name.matches("^\\d.*")) { + String modelName = "Model" + name; // e.g. 200Response => Model200Response (after camelize) + LOGGER.warn(name + " (model name starts with number) cannot be used as model name. Renamed to " + modelName); + return modelName; + } + + return name; + } + + @Override + public String toModelFilename(String name) { + // should be the same as the model name + return toModelName(name); + } + + @Override + public String toModelImport(String name) { + return name; + } + + @Override + public String toApiImport(String name) { + return toApiName(name); + } + + @Override + public String getTypeDeclaration(Schema p) { + if (ModelUtils.isArraySchema(p)) { + ArraySchema ap = (ArraySchema) p; + Schema inner = ap.getItems(); + return "[" + getTypeDeclaration(inner) + "]"; + } else if (ModelUtils.isMapSchema(p)) { + Schema inner = ModelUtils.getAdditionalProperties(p); + return "{String: " + getTypeDeclaration(inner) + "}"; + } + return super.getTypeDeclaration(p); + } + + @Override + public String toDefaultValue(Schema p) { + if (ModelUtils.isBooleanSchema(p)) { + if (p.getDefault() != null) { + return p.getDefault().toString(); + } + } else if (ModelUtils.isDateSchema(p)) { + // TODO + } else if (ModelUtils.isDateTimeSchema(p)) { + // TODO + } else if (ModelUtils.isNumberSchema(p)) { + if (p.getDefault() != null) { + return p.getDefault().toString(); + } + } else if (ModelUtils.isIntegerSchema(p)) { + if (p.getDefault() != null) { + return p.getDefault().toString(); + } + } else if (ModelUtils.isStringSchema(p)) { + if (p.getDefault() != null) { + return "'" + p.getDefault() + "'"; + } + } + + return null; + } + + public void setModelPropertyNaming(String naming) { + if ("original".equals(naming) || "camelCase".equals(naming) || + "PascalCase".equals(naming) || "snake_case".equals(naming)) { + this.modelPropertyNaming = naming; + } else { + throw new IllegalArgumentException("Invalid model property naming '" + + naming + "'. Must be 'original', 'camelCase', " + + "'PascalCase' or 'snake_case'"); + } + } + + @Override + public String toDefaultValueWithParam(String name, Schema p) { + String type = normalizeType(getTypeDeclaration(p)); + if (!StringUtils.isEmpty(p.get$ref())) { + return " = " + type + ".constructFromObject(data['" + name + "']);"; + } else { + return " = ApiClient.convertToType(data['" + name + "'], " + type + ");"; + } + } + + @Override + public void setParameterExampleValue(CodegenParameter p) { + String example; + + if (p.defaultValue == null) { + example = p.example; + } else { + example = p.defaultValue; + } + + String type = p.baseType; + if (type == null) { + type = p.dataType; + } + + if (Boolean.TRUE.equals(p.isInteger)) { + if (example == null) { + example = "56"; + } + } else if (Boolean.TRUE.equals(p.isLong)) { + if (example == null) { + example = "789"; + } + } else if (Boolean.TRUE.equals(p.isDouble) + || Boolean.TRUE.equals(p.isFloat) + || Boolean.TRUE.equals(p.isNumber)) { + if (example == null) { + example = "3.4"; + } + } else if (Boolean.TRUE.equals(p.isBoolean)) { + if (example == null) { + example = "true"; + } + } else if (Boolean.TRUE.equals(p.isFile) || Boolean.TRUE.equals(p.isBinary)) { + if (example == null) { + example = "/path/to/file"; + } + example = "\"" + escapeText(example) + "\""; + } else if (Boolean.TRUE.equals(p.isDate)) { + if (example == null) { + example = "2013-10-20"; + } + example = "new Date(\"" + escapeText(example) + "\")"; + } else if (Boolean.TRUE.equals(p.isDateTime)) { + if (example == null) { + example = "2013-10-20T19:20:30+01:00"; + } + example = "new Date(\"" + escapeText(example) + "\")"; + } else if (Boolean.TRUE.equals(p.isString)) { + if (example == null) { + example = p.paramName + "_example"; + } + example = "\"" + escapeText(example) + "\""; + + } else if (!languageSpecificPrimitives.contains(type)) { + // type is a model class, e.g. User + example = "new " + moduleName + "." + type + "()"; + } + + // container + if (Boolean.TRUE.equals(p.isListContainer)) { + example = setPropertyExampleValue(p.items); + example = "[" + example + "]"; + } else if (Boolean.TRUE.equals(p.isMapContainer)) { + example = setPropertyExampleValue(p.items); + example = "{key: " + example + "}"; + } else if (example == null) { + example = "null"; + } + + p.example = example; + } + + protected String setPropertyExampleValue(CodegenProperty p) { + String example; + + if (p == null) { + return "null"; + } + + if (p.defaultValue == null) { + example = p.example; + } else { + example = p.defaultValue; + } + + String type = p.baseType; + if (type == null) { + type = p.dataType; + } + + if (Boolean.TRUE.equals(p.isInteger)) { + if (example == null) { + example = "56"; + } + } else if (Boolean.TRUE.equals(p.isLong)) { + if (example == null) { + example = "789"; + } + } else if (Boolean.TRUE.equals(p.isDouble) + || Boolean.TRUE.equals(p.isFloat) + || Boolean.TRUE.equals(p.isNumber)) { + if (example == null) { + example = "3.4"; + } + } else if (Boolean.TRUE.equals(p.isBoolean)) { + if (example == null) { + example = "true"; + } + } else if (Boolean.TRUE.equals(p.isFile) || Boolean.TRUE.equals(p.isBinary)) { + if (example == null) { + example = "/path/to/file"; + } + example = "\"" + escapeText(example) + "\""; + } else if (Boolean.TRUE.equals(p.isDate)) { + if (example == null) { + example = "2013-10-20"; + } + example = "new Date(\"" + escapeText(example) + "\")"; + } else if (Boolean.TRUE.equals(p.isDateTime)) { + if (example == null) { + example = "2013-10-20T19:20:30+01:00"; + } + example = "new Date(\"" + escapeText(example) + "\")"; + } else if (Boolean.TRUE.equals(p.isString)) { + if (example == null) { + example = p.name + "_example"; + } + example = "\"" + escapeText(example) + "\""; + + } else if (!languageSpecificPrimitives.contains(type)) { + // type is a model class, e.g. User + example = "new " + moduleName + "." + type + "()"; + } + + return example; + } + + /** + * Normalize type by wrapping primitive types with single quotes. + * + * @param type Primitive type + * @return Normalized type + */ + private String normalizeType(String type) { + return type.replaceAll("\\b(Boolean|Integer|Number|String|Date|Blob)\\b", "'$1'"); + } + + @Override + public String getSchemaType(Schema p) { + String openAPIType = super.getSchemaType(p); + String type = null; + if (typeMapping.containsKey(openAPIType)) { + type = typeMapping.get(openAPIType); + if (!needToImport(type)) { + return type; + } + } else { + type = openAPIType; + } + if (null == type) { + LOGGER.error("No Type defined for Schema " + p); + } + return toModelName(type); + } + + @Override + public String toOperationId(String operationId) { + // throw exception if method name is empty + if (StringUtils.isEmpty(operationId)) { + throw new RuntimeException("Empty method/operation name (operationId) not allowed"); + } + + operationId = camelize(sanitizeName(operationId), true); + + // method name cannot use reserved keyword, e.g. return + if (isReservedWord(operationId)) { + String newOperationId = camelize("call_" + operationId, true); + LOGGER.warn(operationId + " (reserved word) cannot be used as method name. Renamed to " + newOperationId); + return newOperationId; + } + + // operationId starts with a number + if (operationId.matches("^\\d.*")) { + String newOperationId = camelize("call_" + operationId, true); + LOGGER.warn(operationId + " (starting with a number) cannot be used as method name. Renamed to " + newOperationId); + return newOperationId; + } + + return operationId; + } + + @Override + public CodegenModel fromModel(String name, Schema model) { + + // TODO: 5.0: Remove the camelCased vendorExtension below and ensure templates use the newer property naming. + once(LOGGER).warn("4.3.0 has deprecated the use of vendor extensions which don't follow lower-kebab casing standards with x- prefix."); + + Map allDefinitions = ModelUtils.getSchemas(this.openAPI); + CodegenModel codegenModel = super.fromModel(name, model); + + if (allDefinitions != null && codegenModel != null && codegenModel.parent != null && codegenModel.hasEnums) { + final Schema parentModel = allDefinitions.get(codegenModel.parentSchema); + final CodegenModel parentCodegenModel = super.fromModel(codegenModel.parent, parentModel); + codegenModel = JavascriptApolloClientCodegen.reconcileInlineEnums(codegenModel, parentCodegenModel); + } + if (ModelUtils.isArraySchema(model)) { + ArraySchema am = (ArraySchema) model; + if (codegenModel != null && am.getItems() != null) { + String itemType = getSchemaType(am.getItems()); + codegenModel.vendorExtensions.put("x-isArray", true); // TODO: 5.0 Remove + codegenModel.vendorExtensions.put("x-is-array", true); + codegenModel.vendorExtensions.put("x-itemType", itemType); // TODO: 5.0 Remove + codegenModel.vendorExtensions.put("x-item-type", itemType); + } + } else if (ModelUtils.isMapSchema(model)) { + if (codegenModel != null && ModelUtils.getAdditionalProperties(model) != null) { + String itemType = getSchemaType(ModelUtils.getAdditionalProperties(model)); + codegenModel.vendorExtensions.put("x-isMap", true); // TODO: 5.0 Remove + codegenModel.vendorExtensions.put("x-is-map", true); + codegenModel.vendorExtensions.put("x-itemType", itemType); // TODO: 5.0 Remove + codegenModel.vendorExtensions.put("x-item-type", itemType); + } else { + String type = model.getType(); + if (codegenModel != null && isPrimitiveType(type)) { + codegenModel.vendorExtensions.put("x-isPrimitive", true); // TODO: 5.0 Remove + codegenModel.vendorExtensions.put("x-is-primitive", true); + } + } + } + + return codegenModel; + } + + /* + private String sanitizePath(String p) { + //prefer replace a ', instead of a fuLL URL encode for readability + return p.replaceAll("'", "%27"); + }*/ + + private String trimBrackets(String s) { + if (s != null) { + int beginIdx = s.charAt(0) == '[' ? 1 : 0; + int endIdx = s.length(); + if (s.charAt(endIdx - 1) == ']') + endIdx--; + return s.substring(beginIdx, endIdx); + } + return null; + } + + private String getJSDocType(CodegenModel cm, CodegenProperty cp) { + if (Boolean.TRUE.equals(cp.isContainer)) { + if (cp.containerType.equals("array")) + return "Array.<" + cp.items + ">"; + else if (cp.containerType.equals("map")) + return "Object."; + } + String dataType = trimBrackets(cp.datatypeWithEnum); + if (cp.isEnum) { + dataType = cm.classname + '.' + dataType; + } + return dataType; + } + + private String getJSDocType(CodegenParameter cp) { + String dataType = trimBrackets(cp.dataType); + if (Boolean.TRUE.equals(cp.isListContainer)) { + return "Array.<" + dataType + ">"; + } else if (Boolean.TRUE.equals(cp.isMapContainer)) { + return "Object."; + } + return dataType; + } + + private String getJSDocType(CodegenOperation co) { + String returnType = trimBrackets(co.returnType); + if (returnType != null) { + if (Boolean.TRUE.equals(co.isListContainer)) { + return "Array.<" + returnType + ">"; + } else if (Boolean.TRUE.equals(co.isMapContainer)) { + return "Object."; + } + } + return returnType; + } + + private boolean isPrimitiveType(String type) { + final String[] primitives = {"number", "integer", "string", "boolean", "null"}; + return Arrays.asList(primitives).contains(type); + } + + @SuppressWarnings("unchecked") + @Override + public Map postProcessOperationsWithModels(Map objs, List allModels) { + // Generate and store argument list string of each operation into + // vendor-extension: x-codegen-argList. + Map operations = (Map) objs.get("operations"); + + // TODO: 5.0: Remove the camelCased vendorExtension below and ensure templates use the newer property naming. + once(LOGGER).warn("4.3.0 has deprecated the use of vendor extensions which don't follow lower-kebab casing standards with x- prefix."); + + if (operations != null) { + List ops = (List) operations.get("operation"); + for (CodegenOperation operation : ops) { + List argList = new ArrayList<>(); + boolean hasOptionalParams = false; + for (CodegenParameter p : operation.allParams) { + if (p.required) { + argList.add(p.paramName); + } else { + hasOptionalParams = true; + } + } + + if (operation.servers != null && !operation.servers.isEmpty()) { + // add optional parameter for servers (e.g. index) + hasOptionalParams = true; + } + + if (hasOptionalParams) { + argList.add("opts"); + } + + String joinedArgList = StringUtils.join(argList, ", "); + operation.vendorExtensions.put("x-codegen-argList", joinedArgList); // TODO: 5.0 Remove + operation.vendorExtensions.put("x-codegen-arg-list", joinedArgList); + operation.vendorExtensions.put("x-codegen-hasOptionalParams", hasOptionalParams); // TODO: 5.0 Remove + operation.vendorExtensions.put("x-codegen-has-optional-params", hasOptionalParams); + + // Store JSDoc type specification into vendor-extension: x-jsdoc-type. + for (CodegenParameter cp : operation.allParams) { + String jsdocType = getJSDocType(cp); + cp.vendorExtensions.put("x-jsdoc-type", jsdocType); + } + String jsdocType = getJSDocType(operation); + operation.vendorExtensions.put("x-jsdoc-type", jsdocType); + + // Format the return type correctly + if (operation.returnType != null) { + operation.vendorExtensions.put("x-return-type", normalizeType(operation.returnType)); + } + } + } + return objs; + } + + @SuppressWarnings("unchecked") + @Override + public Map postProcessModels(Map objs) { + objs = super.postProcessModelsEnum(objs); + List models = (List) objs.get("models"); + + // TODO: 5.0: Remove the camelCased vendorExtension below and ensure templates use the newer property naming. + once(LOGGER).warn("4.3.0 has deprecated the use of vendor extensions which don't follow lower-kebab casing standards with x- prefix."); + + for (Object _mo : models) { + Map mo = (Map) _mo; + CodegenModel cm = (CodegenModel) mo.get("model"); + + // Collect each model's required property names in *document order*. + // NOTE: can't use 'mandatory' as it is built from ModelImpl.getRequired(), which sorts names + // alphabetically and in any case the document order of 'required' and 'properties' can differ. + List required = new ArrayList<>(); + List allRequired = supportsInheritance || supportsMixins ? new ArrayList<>() : required; + cm.vendorExtensions.put("x-required", required); + cm.vendorExtensions.put("x-all-required", allRequired); + + for (CodegenProperty var : cm.vars) { + // Add JSDoc @type value for this property. + String jsDocType = getJSDocType(cm, var); + var.vendorExtensions.put("x-jsdoc-type", jsDocType); + + if (Boolean.TRUE.equals(var.required)) { + required.add(var); + } + } + + for (CodegenProperty var : cm.allVars) { + // Add JSDoc @type value for this property. + String jsDocType = getJSDocType(cm, var); + var.vendorExtensions.put("x-jsdoc-type", jsDocType); + + if (Boolean.TRUE.equals(var.required)) { + required.add(var); + } + } + + if (supportsInheritance || supportsMixins) { + for (CodegenProperty var : cm.allVars) { + if (Boolean.TRUE.equals(var.required)) { + allRequired.add(var); + } + } + } + + // set vendor-extension: x-codegen-hasMoreRequired + CodegenProperty lastRequired = null; + for (CodegenProperty var : cm.vars) { + if (var.required) { + lastRequired = var; + } + } + for (CodegenProperty var : cm.vars) { + Optional.ofNullable(lastRequired).ifPresent(_lastRequired -> { + if (var == _lastRequired) { + var.vendorExtensions.put("x-codegen-hasMoreRequired", false); // TODO: 5.0 Remove + var.vendorExtensions.put("x-codegen-has-more-required", false); + } else if (var.required) { + var.vendorExtensions.put("x-codegen-hasMoreRequired", true); // TODO: 5.0 Remove + var.vendorExtensions.put("x-codegen-has-more-required", true); + } + }); + } + } + return objs; + } + + @Override + protected boolean needToImport(String type) { + return !defaultIncludes.contains(type) + && !languageSpecificPrimitives.contains(type); + } + + private static CodegenModel reconcileInlineEnums(CodegenModel codegenModel, CodegenModel parentCodegenModel) { + // This generator uses inline classes to define enums, which breaks when + // dealing with models that have subTypes. To clean this up, we will analyze + // the parent and child models, look for enums that match, and remove + // them from the child models and leave them in the parent. + // Because the child models extend the parents, the enums will be available via the parent. + + // Only bother with reconciliation if the parent model has enums. + if (parentCodegenModel.hasEnums) { + + // Get the properties for the parent and child models + final List parentModelCodegenProperties = parentCodegenModel.vars; + List codegenProperties = codegenModel.vars; + + // Iterate over all of the parent model properties + boolean removedChildEnum = false; + for (CodegenProperty parentModelCodegenPropery : parentModelCodegenProperties) { + // Look for enums + if (parentModelCodegenPropery.isEnum) { + // Now that we have found an enum in the parent class, + // and search the child class for the same enum. + Iterator iterator = codegenProperties.iterator(); + while (iterator.hasNext()) { + CodegenProperty codegenProperty = iterator.next(); + if (codegenProperty.isEnum && codegenProperty.equals(parentModelCodegenPropery)) { + // We found an enum in the child class that is + // a duplicate of the one in the parent, so remove it. + iterator.remove(); + removedChildEnum = true; + } + } + } + } + + if (removedChildEnum) { + // If we removed an entry from this model's vars, we need to ensure hasMore is updated + int count = 0, numVars = codegenProperties.size(); + for (CodegenProperty codegenProperty : codegenProperties) { + count += 1; + codegenProperty.hasMore = (count < numVars) ? true : false; + } + codegenModel.vars = codegenProperties; + } + } + + return codegenModel; + } + + @Override + public String toEnumName(CodegenProperty property) { + return sanitizeName(camelize(property.name)) + "Enum"; + } + + @Override + public String toEnumVarName(String value, String datatype) { + if (value.length() == 0) { + return "empty"; + } + + // for symbol, e.g. $, # + if (getSymbolName(value) != null) { + return (getSymbolName(value)).toUpperCase(Locale.ROOT); + } + + return value; + } + + @Override + public String toEnumValue(String value, String datatype) { + if ("Integer".equals(datatype) || "Number".equals(datatype)) { + return value; + } else { + return "\"" + escapeText(value) + "\""; + } + } + + + @Override + public String escapeQuotationMark(String input) { + if (input == null) { + return ""; + } + // remove ', " to avoid code injection + return input.replace("\"", "").replace("'", ""); + } + + @Override + public String escapeUnsafeCharacters(String input) { + if (input == null) { + return ""; + } + return input.replace("*/", "*_/").replace("/*", "/_*"); + } + + @Override + public void postProcessFile(File file, String fileType) { + if (file == null) { + return; + } + + String jsPostProcessFile = System.getenv("JS_POST_PROCESS_FILE"); + if (StringUtils.isEmpty(jsPostProcessFile)) { + return; // skip if JS_POST_PROCESS_FILE env variable is not defined + } + + // only process files with js extension + if ("js".equals(FilenameUtils.getExtension(file.toString()))) { + String command = jsPostProcessFile + " " + file.toString(); + try { + Process p = Runtime.getRuntime().exec(command); + p.waitFor(); + int exitValue = p.exitValue(); + if (exitValue != 0) { + LOGGER.error("Error running the command ({}). Exit code: {}", command, exitValue); + } + LOGGER.info("Successfully executed: " + command); + } catch (Exception e) { + LOGGER.error("Error running the command ({}). Exception: {}", command, e.getMessage()); + } + } + } +} diff --git a/modules/openapi-generator/src/main/resources/Javascript-Apollo/.babelrc.mustache b/modules/openapi-generator/src/main/resources/Javascript-Apollo/.babelrc.mustache new file mode 100644 index 00000000000..c73df9d50b4 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/Javascript-Apollo/.babelrc.mustache @@ -0,0 +1,33 @@ +{ + "presets": [ + "@babel/preset-env" + ], + "plugins": [ + "@babel/plugin-syntax-dynamic-import", + "@babel/plugin-syntax-import-meta", + "@babel/plugin-proposal-class-properties", + "@babel/plugin-proposal-json-strings", + [ + "@babel/plugin-proposal-decorators", + { + "legacy": true + } + ], + "@babel/plugin-proposal-function-sent", + "@babel/plugin-proposal-export-namespace-from", + "@babel/plugin-proposal-numeric-separator", + "@babel/plugin-proposal-throw-expressions", + "@babel/plugin-proposal-export-default-from", + "@babel/plugin-proposal-logical-assignment-operators", + "@babel/plugin-proposal-optional-chaining", + [ + "@babel/plugin-proposal-pipeline-operator", + { + "proposal": "minimal" + } + ], + "@babel/plugin-proposal-nullish-coalescing-operator", + "@babel/plugin-proposal-do-expressions", + "@babel/plugin-proposal-function-bind" + ] +} diff --git a/modules/openapi-generator/src/main/resources/Javascript-Apollo/ApiClient.mustache b/modules/openapi-generator/src/main/resources/Javascript-Apollo/ApiClient.mustache new file mode 100644 index 00000000000..445e1cef2b8 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/Javascript-Apollo/ApiClient.mustache @@ -0,0 +1,299 @@ +{{>licenseInfo}} + +import RESTDataSource from 'apollo-datasource-rest'; + +{{#emitJSDoc}}/** +* @module {{#invokerPackage}}{{invokerPackage}}/{{/invokerPackage}}ApiClient +* @version {{projectVersion}} +*/ + +/** +* Manages low level client-server communications, parameter marshalling, etc. There should not be any need for an +* application to use this class directly - the *Api and model classes provide the public API for the service. +* @alias module:{{#invokerPackage}}{{invokerPackage}}/{{/invokerPackage}}ApiClient +* @class +*/{{/emitJSDoc}} +export default class ApiClient extends RESTDataSource { + constructor() { + super() + + {{#emitJSDoc}}/** + * The authentication methods to be included for all API calls. + * @type {Array.} + */{{/emitJSDoc}}{{=< >=}} + this.authentications = { +<#authMethods> +<#isBasic> +<#isBasicBasic> + '': {type: 'basic'}<^-last>, + +<#isBasicBearer> + '': {type: 'bearer'}<^-last>,<#bearerFormat> // <&.> + + +<#isApiKey> + '': {type: 'apiKey', 'in': <#isKeyInHeader>'header'<^isKeyInHeader>'query', name: ''}<^-last>, + +<#isOAuth> + '': {type: 'oauth2'}<^-last>, + + + } + } + + paramToString(param) { + if (param == undefined || param == null) { + return ''; + } + if (param instanceof Date) { + return param.toJSON(); + } + + return param.toString(); + } + + parametrizePath(path, pathParams) { + return url.replace(/\{([\w-]+)\}/g, (fullMatch, key) => { + var value; + if (pathParams.hasOwnProperty(key)) { + value = this.paramToString(pathParams[key]); + } else { + value = fullMatch; + } + + return encodeURIComponent(value); + }); + } + + isFileParam(param) { + // fs.ReadStream in Node.js and Electron (but not in runtime like browserify) + if (typeof require === 'function') { + let fs; + try { + fs = require('fs'); + } catch (err) {} + if (fs && fs.ReadStream && param instanceof fs.ReadStream) { + return true; + } + } + + // Buffer in Node.js + if (typeof Buffer === 'function' && param instanceof Buffer) { + return true; + } + + // Blob in browser + if (typeof Blob === 'function' && param instanceof Blob) { + return true; + } + + // File in browser (it seems File object is also instance of Blob, but keep this for safe) + if (typeof File === 'function' && param instanceof File) { + return true; + } + + return false; + } + + normalizeParams(params) { + var newParams = {}; + for (var key in params) { + if (params.hasOwnProperty(key) && params[key] != undefined && params[key] != null) { + var value = params[key]; + if (this.isFileParam(value) || Array.isArray(value)) { + newParams[key] = value; + } else { + newParams[key] = this.paramToString(value); + } + } + } + + return newParams; + } + + buildCollectionParam(param, collectionFormat) { + if (param == null) { + return null; + } + switch (collectionFormat) { + case 'csv': + return param.map(this.paramToString).join(','); + case 'ssv': + return param.map(this.paramToString).join(' '); + case 'tsv': + return param.map(this.paramToString).join('\t'); + case 'pipes': + return param.map(this.paramToString).join('|'); + case 'multi': + //return the array directly as SuperAgent will handle it as expected + return param.map(this.paramToString); + default: + throw new Error('Unknown collection format: ' + collectionFormat); + } + } + + applyAuthOptions(fetchOptions, authNames) { + fetchOptions.headers = fetchOptions.headers || {}; + + authNames.forEach((authName) => { + var auth = this.authentications[authName]; + switch (auth.type) { + case 'basic': + if (auth.username || auth.password) { + fetchOptions.headers['Authorization'] = 'Basic ' + base64.encode(auth.username + ":" + auth.password); + } + + break; + case 'bearer': + case 'oauth2': + if (auth.accessToken) { + fetchOptions.headers['Authorization'] = 'Bearer ' + auth.accessToken; + } + + break; + case 'apiKey': + if (auth.apiKey) { + var data = {}; + if (auth.apiKeyPrefix) { + data[auth.name] = auth.apiKeyPrefix + ' ' + auth.apiKey; + } else { + data[auth.name] = auth.apiKey; + } + + if (auth['in'] === 'header') { + Object.assign(fetchOptions.headers, data); + } else { + Object.assign(fetchOptions.params, data); + } + } + + break; + default: + throw new Error('Unknown authentication type: ' + auth.type); + } + }); + } + + async callApi(path, httpMethod, pathParams, + queryParams, headerParams, formParams, bodyParam, authNames, + returnType) { + + var parameterizedPath = this.parametrizePath(path, pathParams); + var fetchOptions = { + headers: headerParams, + params: queryParams + }; + + this.applyAuthOptions(fetchOptions, authNames); + + var body = null; + + if (bodyParam !== null && bodyParam !== undefined) { + body = bodyParam; + } else if (formParams !== null && formParams !== undefined) { + var _formParams = this.normalizeParams(formParams); + for (var key in _formParams) { + if (_formParams.hasOwnProperty(key)) { + body[key] = _formParams[key]; + } + } + } + + var response; + var httpMethodFn = httpMethod.toLowerCase(); + + if (httpMethodFn == 'get' || httpMethodFn == 'delete') { + response = await this[httpMethodFn](parameterizedPath, fetchOptions); + } else { + response = await this[httpMethodFn](parameterizedPath, body, fetchOptions) + } + + var convertedResponse = ApiClient.convertToType(response, returnType); + return convertedResponse; + } + + static parseDate(str) { + return new Date(str); + } + + static convertToType(data, type) { + if (data === null || data === undefined) + return data + + switch (type) { + case 'Boolean': + return Boolean(data); + case 'Integer': + return parseInt(data, 10); + case 'Number': + return parseFloat(data); + case 'String': + return String(data); + case 'Date': + return ApiClient.parseDate(String(data)); + case 'Blob': + return data; + default: + if (type === Object) { + // generic object, return directly + return data; + } else if (typeof type.constructFromObject === 'function') { + // for model type like User and enum class + return type.constructFromObject(data); + } else if (Array.isArray(type)) { + // for array type like: ['String'] + var itemType = type[0]; + + return data.map((item) => { + return ApiClient.convertToType(item, itemType); + }); + } else if (typeof type === 'object') { + // for plain object type like: {'String': 'Integer'} + var keyType, valueType; + for (var k in type) { + if (type.hasOwnProperty(k)) { + keyType = k; + valueType = type[k]; + break; + } + } + + var result = {}; + for (var k in data) { + if (data.hasOwnProperty(k)) { + var key = ApiClient.convertToType(k, keyType); + var value = ApiClient.convertToType(data[k], valueType); + result[key] = value; + } + } + + return result; + } else { + // for unknown type, return the data directly + return data; + } + } + } + + static constructFromObject(data, obj, itemType) { + if (Array.isArray(data)) { + for (var i = 0; i < data.length; i++) { + if (data.hasOwnProperty(i)) + obj[i] = ApiClient.convertToType(data[i], itemType); + } + } else { + for (var k in data) { + if (data.hasOwnProperty(k)) + obj[k] = ApiClient.convertToType(data[k], itemType); + } + } + }; +} + +ApiClient.CollectionFormatEnum = { + CSV: ',', + SSV: ' ', + TSV: '\t', + PIPES: '|', + MULTI: 'multi' +}; diff --git a/modules/openapi-generator/src/main/resources/Javascript-Apollo/README.mustache b/modules/openapi-generator/src/main/resources/Javascript-Apollo/README.mustache new file mode 100644 index 00000000000..dd4a6721a2d --- /dev/null +++ b/modules/openapi-generator/src/main/resources/Javascript-Apollo/README.mustache @@ -0,0 +1,226 @@ +# {{projectName}} + +{{moduleName}} - JavaScript client for {{projectName}} +{{#appDescriptionWithNewLines}} +{{{appDescriptionWithNewLines}}} +{{/appDescriptionWithNewLines}} +This SDK is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project: + +- API version: {{appVersion}} +- Package version: {{projectVersion}} +{{^hideGenerationTimestamp}} +- Build date: {{generatedDate}} +{{/hideGenerationTimestamp}} +- Build package: {{generatorClass}} +{{#infoUrl}} +For more information, please visit [{{{infoUrl}}}]({{{infoUrl}}}) +{{/infoUrl}} + +## Installation + +### For [Node.js](https://nodejs.org/) + +#### npm + +To publish the library as a [npm](https://www.npmjs.com/), please follow the procedure in ["Publishing npm packages"](https://docs.npmjs.com/getting-started/publishing-npm-packages). + +Then install it via: + +```shell +npm install {{{projectName}}} --save +``` + +Finally, you need to build the module: + +```shell +npm run build +``` + +##### Local development + +To use the library locally without publishing to a remote npm registry, first install the dependencies by changing into the directory containing `package.json` (and this README). Let's call this `JAVASCRIPT_CLIENT_DIR`. Then run: + +```shell +npm install +``` + +Next, [link](https://docs.npmjs.com/cli/link) it globally in npm with the following, also from `JAVASCRIPT_CLIENT_DIR`: + +```shell +npm link +``` + +To use the link you just defined in your project, switch to the directory you want to use your {{{projectName}}} from, and run: + +```shell +npm link /path/to/ +``` + +Finally, you need to build the module: + +```shell +npm run build +``` + +#### git + +If the library is hosted at a git repository, e.g.https://github.com/{{#gitUserId}}{{.}}{{/gitUserId}}{{^gitUserId}}YOUR_USERNAME{{/gitUserId}}/{{#gitRepoId}}{{.}}{{/gitRepoId}}{{^gitRepoId}}{{projectName}}{{/gitRepoId}} +then install it via: + +```shell + npm install {{#gitUserId}}{{.}}{{/gitUserId}}{{^gitUserId}}YOUR_USERNAME{{/gitUserId}}/{{#gitRepoId}}{{.}}{{/gitRepoId}}{{^gitRepoId}}{{projectName}}{{/gitRepoId}} --save +``` + +### For browser + +The library also works in the browser environment via npm and [browserify](http://browserify.org/). After following +the above steps with Node.js and installing browserify with `npm install -g browserify`, +perform the following (assuming *main.js* is your entry file): + +```shell +browserify main.js > bundle.js +``` + +Then include *bundle.js* in the HTML pages. + +### Webpack Configuration + +Using Webpack you may encounter the following error: "Module not found: Error: +Cannot resolve module", most certainly you should disable AMD loader. Add/merge +the following section to your webpack config: + +```javascript +module: { + rules: [ + { + parser: { + amd: false + } + } + ] +} +``` + +## Getting Started + +Please follow the [installation](#installation) instruction and execute the following JS code: + +```javascript +var {{{moduleName}}} = require('{{{projectName}}}'); +{{#apiInfo}}{{#apis}}{{#-first}}{{#operations}}{{#operation}}{{#-first}} +{{#hasAuthMethods}} +var defaultClient = {{{moduleName}}}.ApiClient.instance; +{{#authMethods}} +{{#isBasic}} +{{#isBasicBasic}} +// Configure HTTP basic authorization: {{{name}}} +var {{{name}}} = defaultClient.authentications['{{{name}}}']; +{{{name}}}.username = 'YOUR USERNAME' +{{{name}}}.password = 'YOUR PASSWORD' +{{/isBasicBasic}} +{{#isBasicBearer}} +// Configure Bearer{{#bearerFormat}} ({{{.}}}){{/bearerFormat}} access token for authorization: {{{name}}} +var {{{name}}} = defaultClient.authentications['{{{name}}}']; +{{{name}}}.accessToken = "YOUR ACCESS TOKEN" +{{/isBasicBearer}} +{{/isBasic}} +{{#isApiKey}} +// Configure API key authorization: {{{name}}} +var {{{name}}} = defaultClient.authentications['{{{name}}}']; +{{{name}}}.apiKey = "YOUR API KEY" +// Uncomment the following line to set a prefix for the API key, e.g. "Token" (defaults to null) +//{{{name}}}.apiKeyPrefix['{{{keyParamName}}}'] = "Token" +{{/isApiKey}} +{{#isOAuth}} +// Configure OAuth2 access token for authorization: {{{name}}} +var {{{name}}} = defaultClient.authentications['{{{name}}}']; +{{{name}}}.accessToken = "YOUR ACCESS TOKEN" +{{/isOAuth}} +{{/authMethods}} +{{/hasAuthMethods}} + +var api = new {{{moduleName}}}.{{{classname}}}() +{{#hasParams}} +{{#requiredParams}} +var {{{paramName}}} = {{{example}}}; // {{=< >=}}{<&dataType>}<={{ }}=> {{{description}}} +{{/requiredParams}} +{{#optionalParams}} +{{#-first}} +var opts = { +{{/-first}} + '{{{paramName}}}': {{{example}}}{{^-last}},{{/-last}} // {{=< >=}}{<&dataType>}<={{ }}=> {{{description}}} +{{#-last}} +}; +{{/-last}} +{{/optionalParams}} +{{/hasParams}} +{{#usePromises}} +api.{{{operationId}}}({{#requiredParams}}{{{paramName}}}{{^-last}}, {{/-last}}{{/requiredParams}}{{#optionalParams}}{{#-last}}{{#hasRequiredParams}}, {{/hasRequiredParams}}opts{{/-last}}{{/optionalParams}}).then(function({{#returnType}}data{{/returnType}}) { + {{#returnType}}console.log('API called successfully. Returned data: ' + data);{{/returnType}}{{^returnType}}console.log('API called successfully.');{{/returnType}} +}, function(error) { + console.error(error); +}); + +{{/usePromises}} +{{^usePromises}} +var callback = function(error, data, response) { + if (error) { + console.error(error); + } else { + {{#returnType}}console.log('API called successfully. Returned data: ' + data);{{/returnType}}{{^returnType}}console.log('API called successfully.');{{/returnType}} + } +}; +api.{{{operationId}}}({{#requiredParams}}{{{paramName}}}{{^-last}}, {{/-last}}{{/requiredParams}}{{#optionalParams}}{{#-last}}{{#hasRequiredParams}}, {{/hasRequiredParams}}opts{{/-last}}{{/optionalParams}}{{#hasParams}}, {{/hasParams}}callback); +{{/usePromises}} +{{/-first}}{{/operation}}{{/operations}}{{/-first}}{{/apis}}{{/apiInfo}} +``` + +## Documentation for API Endpoints + +All URIs are relative to *{{basePath}}* + +Class | Method | HTTP request | Description +------------ | ------------- | ------------- | ------------- +{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}*{{moduleName}}.{{classname}}* | [**{{operationId}}**]({{apiDocPath}}{{classname}}.md#{{operationId}}) | **{{httpMethod}}** {{path}} | {{#summary}}{{summary}}{{/summary}} +{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}} + +## Documentation for Models + +{{#models}}{{#model}} - [{{moduleName}}.{{classname}}]({{modelDocPath}}{{classname}}.md) +{{/model}}{{/models}} + +## Documentation for Authorization + +{{^authMethods}} +All endpoints do not require authorization. +{{/authMethods}} +{{#authMethods}} +{{#last}} Authentication schemes defined for the API:{{/last}} + +### {{name}} + +{{#isApiKey}} + +- **Type**: API key +- **API key parameter name**: {{keyParamName}} +- **Location**: {{#isKeyInQuery}}URL query string{{/isKeyInQuery}}{{#isKeyInHeader}}HTTP header{{/isKeyInHeader}} +{{/isApiKey}} +{{#isBasic}} +{{#isBasicBasic}} +- **Type**: HTTP basic authentication +{{/isBasicBasic}} +{{#isBasicBearer}} +- **Type**: Bearer authentication{{#bearerFormat}} ({{{.}}}){{/bearerFormat}} +{{/isBasicBearer}} +{{/isBasic}} +{{#isOAuth}} + +- **Type**: OAuth +- **Flow**: {{flow}} +- **Authorization URL**: {{authorizationUrl}} +- **Scopes**: {{^scopes}}N/A{{/scopes}} +{{#scopes}} - {{scope}}: {{description}} +{{/scopes}} +{{/isOAuth}} + +{{/authMethods}} diff --git a/modules/openapi-generator/src/main/resources/Javascript-Apollo/api.mustache b/modules/openapi-generator/src/main/resources/Javascript-Apollo/api.mustache new file mode 100644 index 00000000000..be5f9f1d358 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/Javascript-Apollo/api.mustache @@ -0,0 +1,88 @@ +{{>licenseInfo}} + +{{=< >=}} + +import ApiClient from "../ApiClient"; +<#imports>import <&import> from '../<#modelPackage><&modelPackage>/'; + + +<#emitJSDoc>/** +* service. +* @module <#invokerPackage><&invokerPackage>/<#apiPackage><&apiPackage>/ +* @version <&projectVersion> +*/ +export default class <&classname> extends ApiClient { + + <#emitJSDoc>/** + * Constructs a new <&classname>. <#description> + * + * @alias module:<#invokerPackage><&invokerPackage>/<#apiPackage><&apiPackage>/ + * @class + */ + constructor() { + super(); + this.baseURL = <#servers.0>basePath<^servers.0>null; + } + +<#operations><#operation><#emitJSDoc> + /**<#summary> + * <&summary><#notes> + * <¬es><#allParams><#required> + * @param {<&vendorExtensions.x-jsdoc-type>} <¶mName> <&description><#hasOptionalParams> + * @param {Object} opts Optional parameters<#allParams><^required> + * @param {<&vendorExtensions.x-jsdoc-type>} opts.<¶mName> <&description><#defaultValue> (default to <&.>) + <=| |=>* @return {Promise|#returnType|<|&vendorExtensions.x-jsdoc-type|>|/returnType|}|=< >=| + */ + async () { + <#vendorExtensions.x-codegen-has-optional-params> + opts = opts || {}; + + let postBody = <#bodyParam><#required><^required>opts['']<^bodyParam>null; +<#allParams> +<#required> + // verify the required parameter '' is set + if ( === undefined || === null) { + throw new Error("Missing the required parameter '' when calling "); + } + + + + let pathParams = {<#pathParams> + '': <#required><^required>opts['']<#hasMore>, + }; + let queryParams = {<#queryParams> + '': <#collectionFormat>this.buildCollectionParam(<#required><^required>opts[''], '')<^collectionFormat><#required><^required>opts['']<#hasMore>, + }; + let headerParams = {<#headerParams> + '': <#required><^required>opts['']<#hasMore>, + }; + let formParams = {<#formParams> + '': <#collectionFormat>this.buildCollectionParam(<#required><^required>opts[''], '')<^collectionFormat><#required><^required>opts['']<#hasMore>, + }; + + let authNames = [<#authMethods>''<#hasMore>, ]; + let contentTypes = [<#consumes>'<& mediaType>'<#hasMore>, ]; + let accepts = [<#produces>'<& mediaType>'<#hasMore>, ]; + let returnType = <#vendorExtensions.x-return-type><&vendorExtensions.x-return-type><^vendorExtensions.x-return-type>null; + <#servers.0> + let basePaths = [<#servers>''<^-last>, ]; + let basePath = basePaths[0]; // by default use the first one in "servers" defined in OpenAPI + if (typeof opts['_base_path_index'] !== 'undefined') { + if (opts['_base_path_index'] >= basePaths.length || opts['_base_path_index'] < 0) { + throw new Error("Invalid index " + opts['_base_path_index'] + " when selecting the host settings. Must be less than " + basePaths.length); + } + basePath = basePaths[opts['_base_path_index']]; + } + + + + return this.callApi( + '<&path>', '', + pathParams, queryParams, headerParams, formParams, postBody, + authNames, contentTypes, accepts, returnType + ); + } + + +} +<={{ }}=> diff --git a/modules/openapi-generator/src/main/resources/Javascript-Apollo/api_doc.mustache b/modules/openapi-generator/src/main/resources/Javascript-Apollo/api_doc.mustache new file mode 100644 index 00000000000..47f5a32f183 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/Javascript-Apollo/api_doc.mustache @@ -0,0 +1,112 @@ +# {{moduleName}}.{{classname}}{{#description}} + +{{description}}{{/description}} + +All URIs are relative to *{{basePath}}* + +Method | HTTP request | Description +------------- | ------------- | ------------- +{{#operations}}{{#operation}}[**{{operationId}}**]({{classname}}.md#{{operationId}}) | **{{httpMethod}}** {{path}} | {{#summary}}{{summary}}{{/summary}} +{{/operation}}{{/operations}} + +{{#operations}} +{{#operation}} + +## {{operationId}} + +> {{#returnType}}{{returnType}} {{/returnType}}{{operationId}}({{#requiredParams}}{{{paramName}}}{{^-last}}, {{/-last}}{{/requiredParams}}{{#optionalParams}}{{#-last}}{{#hasRequiredParams}}, {{/hasRequiredParams}}opts{{/-last}}{{/optionalParams}}) + +{{summary}}{{#notes}} + +{{notes}}{{/notes}} + +### Example + +```javascript +import {{{moduleName}}} from '{{{projectName}}}'; +{{#hasAuthMethods}} +let defaultClient = {{{moduleName}}}.ApiClient.instance; +{{#authMethods}} +{{#isBasic}} +{{#isBasicBasic}} +// Configure HTTP basic authorization: {{{name}}} +let {{{name}}} = defaultClient.authentications['{{{name}}}']; +{{{name}}}.username = 'YOUR USERNAME'; +{{{name}}}.password = 'YOUR PASSWORD'; +{{/isBasicBasic}} +{{#isBasicBearer}} +// Configure Bearer{{#bearerFormat}} ({{{.}}}){{/bearerFormat}} access token for authorization: {{{name}}} +let {{{name}}} = defaultClient.authentications['{{{name}}}']; +{{{name}}}.accessToken = "YOUR ACCESS TOKEN" +{{/isBasicBearer}} +{{/isBasic}} +{{#isApiKey}} +// Configure API key authorization: {{{name}}} +let {{{name}}} = defaultClient.authentications['{{{name}}}']; +{{{name}}}.apiKey = 'YOUR API KEY'; +// Uncomment the following line to set a prefix for the API key, e.g. "Token" (defaults to null) +//{{{name}}}.apiKeyPrefix = 'Token'; +{{/isApiKey}} +{{#isOAuth}} +// Configure OAuth2 access token for authorization: {{{name}}} +let {{{name}}} = defaultClient.authentications['{{{name}}}']; +{{{name}}}.accessToken = 'YOUR ACCESS TOKEN'; +{{/isOAuth}} +{{/authMethods}} +{{/hasAuthMethods}} + +let apiInstance = new {{{moduleName}}}.{{{classname}}}(); +{{#requiredParams}} +let {{{paramName}}} = {{{example}}}; // {{{dataType}}} | {{{description}}} +{{/requiredParams}} +{{#optionalParams}} +{{#-first}} +let opts = { +{{/-first}} + '{{{paramName}}}': {{{example}}}{{^-last}},{{/-last}} // {{{dataType}}} | {{{description}}} +{{#-last}} +}; +{{/-last}} +{{/optionalParams}} +{{#usePromises}} +apiInstance.{{{operationId}}}({{#requiredParams}}{{{paramName}}}{{^-last}}, {{/-last}}{{/requiredParams}}{{#optionalParams}}{{#-last}}{{#hasRequiredParams}}, {{/hasRequiredParams}}opts{{/-last}}{{/optionalParams}}).then(({{#returnType}}data{{/returnType}}) => { + {{#returnType}}console.log('API called successfully. Returned data: ' + data);{{/returnType}}{{^returnType}}console.log('API called successfully.');{{/returnType}} +}, (error) => { + console.error(error); +}); + +{{/usePromises}} +{{^usePromises}} +apiInstance.{{{operationId}}}({{#requiredParams}}{{{paramName}}}{{^-last}}, {{/-last}}{{/requiredParams}}{{#optionalParams}}{{#-last}}{{#hasRequiredParams}}, {{/hasRequiredParams}}opts{{/-last}}{{/optionalParams}}{{#hasParams}}, {{/hasParams}}(error, data, response) => { + if (error) { + console.error(error); + } else { + {{#returnType}}console.log('API called successfully. Returned data: ' + data);{{/returnType}}{{^returnType}}console.log('API called successfully.');{{/returnType}} + } +}); +{{/usePromises}} +``` + +### Parameters + +{{^allParams}}This endpoint does not need any parameter.{{/allParams}}{{#allParams}}{{#-last}} +Name | Type | Description | Notes +------------- | ------------- | ------------- | -------------{{/-last}}{{/allParams}} +{{#allParams}} **{{paramName}}** | {{#isPrimitiveType}}**{{dataType}}**{{/isPrimitiveType}}{{^isPrimitiveType}}{{#isFile}}**{{dataType}}**{{/isFile}}{{^isFile}}[**{{dataType}}**]({{baseType}}.md){{/isFile}}{{/isPrimitiveType}}| {{description}} | {{^required}}[optional] {{/required}}{{#defaultValue}}[default to {{defaultValue}}]{{/defaultValue}} +{{/allParams}} + +### Return type + +{{#returnType}}{{#returnTypeIsPrimitive}}**{{returnType}}**{{/returnTypeIsPrimitive}}{{^returnTypeIsPrimitive}}[**{{returnType}}**]({{returnBaseType}}.md){{/returnTypeIsPrimitive}}{{/returnType}}{{^returnType}}null (empty response body){{/returnType}} + +### Authorization + +{{^authMethods}}No authorization required{{/authMethods}}{{#authMethods}}[{{name}}](../README.md#{{name}}){{^-last}}, {{/-last}}{{/authMethods}} + +### HTTP request headers + +- **Content-Type**: {{#consumes}}{{{mediaType}}}{{#hasMore}}, {{/hasMore}}{{/consumes}}{{^consumes}}Not defined{{/consumes}} +- **Accept**: {{#produces}}{{{mediaType}}}{{#hasMore}}, {{/hasMore}}{{/produces}}{{^produces}}Not defined{{/produces}} + +{{/operation}} +{{/operations}} diff --git a/modules/openapi-generator/src/main/resources/Javascript-Apollo/api_test.mustache b/modules/openapi-generator/src/main/resources/Javascript-Apollo/api_test.mustache new file mode 100644 index 00000000000..e56b66d47e3 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/Javascript-Apollo/api_test.mustache @@ -0,0 +1,44 @@ +{{>licenseInfo}} +// CommonJS-like environments that support module.exports, like Node. +factory(require('expect.js'), require(process.cwd()+'/src/{{#invokerPackage}}{{invokerPackage}}/{{/invokerPackage}}index')); + +'use strict'; + +var instance; + +beforeEach(function() { + instance = new {{moduleName}}.{{classname}}(); +}); + +var getProperty = function(object, getter, property) { + // Use getter method if present; otherwise, get the property directly. + if (typeof object[getter] === 'function') + return object[getter](); + else + return object[property]; +} + +var setProperty = function(object, setter, property, value) { + // Use setter method if present; otherwise, set the property directly. + if (typeof object[setter] === 'function') + object[setter](value); + else + object[property] = value; +} + +describe('{{classname}}', function() { +{{#operations}} +{{#operation}} + describe('{{operationId}}', function() { + it('should call {{operationId}} successfully', function(done) { + //uncomment below and update the code to test {{operationId}} + //instance.{{operationId}}(function(error) { + // if (error) throw error; + //expect().to.be(); + //}); + done(); + }); + }); +{{/operation}} +{{/operations}} +}); \ No newline at end of file diff --git a/modules/openapi-generator/src/main/resources/Javascript-Apollo/enumClass.mustache b/modules/openapi-generator/src/main/resources/Javascript-Apollo/enumClass.mustache new file mode 100644 index 00000000000..635476d2c2c --- /dev/null +++ b/modules/openapi-generator/src/main/resources/Javascript-Apollo/enumClass.mustache @@ -0,0 +1,15 @@ +{{#emitJSDoc}}/** +* Allowed values for the {{baseName}} property. +* @enum {{=<% %>=}}{<%datatype%>}<%={{ }}=%> +* @readonly +*/{{/emitJSDoc}} +export default {{datatypeWithEnum}} = { +{{#allowableValues}}{{#enumVars}} + {{#emitJSDoc}}/** + * value: {{{value}}} + * @const + */{{/emitJSDoc}} + "{{name}}": {{{value}}}{{^-last}}, + {{/-last}} +{{/enumVars}}{{/allowableValues}} +}; diff --git a/modules/openapi-generator/src/main/resources/Javascript-Apollo/git_push.sh.mustache b/modules/openapi-generator/src/main/resources/Javascript-Apollo/git_push.sh.mustache new file mode 100644 index 00000000000..8b3f689c912 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/Javascript-Apollo/git_push.sh.mustache @@ -0,0 +1,58 @@ +#!/bin/sh +# ref: https://help.github.com/articles/adding-an-existing-project-to-github-using-the-command-line/ +# +# Usage example: /bin/sh ./git_push.sh wing328 openapi-pestore-perl "minor update" "gitlab.com" + +git_user_id=$1 +git_repo_id=$2 +release_note=$3 +git_host=$4 + +if [ "$git_host" = "" ]; then + git_host="{{{gitHost}}}" + echo "[INFO] No command line input provided. Set \$git_host to $git_host" +fi + +if [ "$git_user_id" = "" ]; then + git_user_id="{{{gitUserId}}}" + echo "[INFO] No command line input provided. Set \$git_user_id to $git_user_id" +fi + +if [ "$git_repo_id" = "" ]; then + git_repo_id="{{{gitRepoId}}}" + echo "[INFO] No command line input provided. Set \$git_repo_id to $git_repo_id" +fi + +if [ "$release_note" = "" ]; then + release_note="{{{releaseNote}}}" + echo "[INFO] No command line input provided. Set \$release_note to $release_note" +fi + +# Initialize the local directory as a Git repository +git init + +# Adds the files in the local repository and stages them for commit. +git add . + +# Commits the tracked changes and prepares them to be pushed to a remote repository. +git commit -m "$release_note" + +# Sets the new remote +git_remote=`git remote` +if [ "$git_remote" = "" ]; then # git remote not defined + + if [ "$GIT_TOKEN" = "" ]; then + echo "[INFO] \$GIT_TOKEN (environment variable) is not set. Using the git credential in your environment." + git remote add origin https://${git_host}/${git_user_id}/${git_repo_id}.git + else + git remote add origin https://${git_user_id}:${GIT_TOKEN}@${git_host}/${git_user_id}/${git_repo_id}.git + fi + +fi + +git pull origin master + +# Pushes (Forces) the changes in the local repository up to the remote repository +echo "Git pushing to https://${git_host}/${git_user_id}/${git_repo_id}.git" +git push origin master 2>&1 | grep -v 'To https' + diff --git a/modules/openapi-generator/src/main/resources/Javascript-Apollo/gitignore.mustache b/modules/openapi-generator/src/main/resources/Javascript-Apollo/gitignore.mustache new file mode 100644 index 00000000000..e920c16718d --- /dev/null +++ b/modules/openapi-generator/src/main/resources/Javascript-Apollo/gitignore.mustache @@ -0,0 +1,33 @@ +# Logs +logs +*.log +npm-debug.log* + +# Runtime data +pids +*.pid +*.seed + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directory +node_modules + +# Optional npm cache directory +.npm + +# Optional REPL history +.node_repl_history diff --git a/modules/openapi-generator/src/main/resources/Javascript-Apollo/index.mustache b/modules/openapi-generator/src/main/resources/Javascript-Apollo/index.mustache new file mode 100644 index 00000000000..36b8a2032d1 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/Javascript-Apollo/index.mustache @@ -0,0 +1,58 @@ +{{>licenseInfo}} + +import ApiClient from './ApiClient'; +{{#models}}import {{#model}}{{classFilename}}{{/model}} from './{{#modelPackage}}{{modelPackage}}/{{/modelPackage}}{{importPath}}'; +{{/models}}{{#apiInfo}}{{#apis}}import {{importPath}} from './{{#apiPackage}}{{apiPackage}}/{{/apiPackage}}{{importPath}}'; +{{/apis}}{{/apiInfo}} + +{{#emitJSDoc}}/**{{#projectDescription}} +* {{projectDescription}}.
{{/projectDescription}} +* The index module provides access to constructors for all the classes which comprise the public API. +*

+* An AMD (recommended!) or CommonJS application will generally do something equivalent to the following: +*

+* var {{moduleName}} = require('{{#invokerPackage}}{{invokerPackage}}/{{/invokerPackage}}index'); // See note below*.
+* var xxxSvc = new {{moduleName}}.XxxApi(); // Allocate the API class we're going to use.
+* var yyyModel = new {{moduleName}}.Yyy(); // Construct a model instance.
+* yyyModel.someProperty = 'someValue';
+* ...
+* var zzz = xxxSvc.doSomething(yyyModel); // Invoke the service.
+* ...
+* 
+* *NOTE: For a top-level AMD script, use require(['{{#invokerPackage}}{{invokerPackage}}/{{/invokerPackage}}index'], function(){...}) +* and put the application logic within the callback function. +*

+*

+* A non-AMD browser application (discouraged) might do something like this: +*

+* var xxxSvc = new {{moduleName}}.XxxApi(); // Allocate the API class we're going to use.
+* var yyy = new {{moduleName}}.Yyy(); // Construct a model instance.
+* yyyModel.someProperty = 'someValue';
+* ...
+* var zzz = xxxSvc.doSomething(yyyModel); // Invoke the service.
+* ...
+* 
+*

+* @module {{#invokerPackage}}{{invokerPackage}}/{{/invokerPackage}}index +* @version {{projectVersion}} +*/{{/emitJSDoc}} +export { + {{=< >=}} + <#emitJSDoc>/** + * The ApiClient constructor. + * @property {module:<#invokerPackage>/ApiClient} + */ + ApiClient<#models>, + + <#emitJSDoc>/** + * The model constructor. + * @property {module:<#invokerPackage>/<#modelPackage>/} + */ + <#apiInfo><#apis>, + + <#emitJSDoc>/** + * The service constructor. + * @property {module:<#invokerPackage>/<#apiPackage>/} + */ + +};<={{ }}=> diff --git a/modules/openapi-generator/src/main/resources/Javascript-Apollo/licenseInfo.mustache b/modules/openapi-generator/src/main/resources/Javascript-Apollo/licenseInfo.mustache new file mode 100644 index 00000000000..40cac6d4a3c --- /dev/null +++ b/modules/openapi-generator/src/main/resources/Javascript-Apollo/licenseInfo.mustache @@ -0,0 +1,12 @@ +/** + * {{{appName}}} + * {{{appDescription}}} + * + * {{#version}}The version of the OpenAPI document: {{{version}}}{{/version}} + * {{#infoEmail}}Contact: {{{infoEmail}}}{{/infoEmail}} + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + * + */ diff --git a/modules/openapi-generator/src/main/resources/Javascript-Apollo/mocha.opts b/modules/openapi-generator/src/main/resources/Javascript-Apollo/mocha.opts new file mode 100644 index 00000000000..907011807d6 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/Javascript-Apollo/mocha.opts @@ -0,0 +1 @@ +--timeout 10000 diff --git a/modules/openapi-generator/src/main/resources/Javascript-Apollo/model.mustache b/modules/openapi-generator/src/main/resources/Javascript-Apollo/model.mustache new file mode 100644 index 00000000000..3ddf83730f5 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/Javascript-Apollo/model.mustache @@ -0,0 +1,4 @@ +{{>licenseInfo}} +import ApiClient from '../ApiClient'; +{{#imports}}import {{import}} from './{{import}}'; +{{/imports}}{{#models}}{{#model}}{{#isEnum}}{{>partial_model_enum_class}}{{/isEnum}}{{^isEnum}}{{>partial_model_generic}}{{/isEnum}}{{/model}}{{/models}} diff --git a/modules/openapi-generator/src/main/resources/Javascript-Apollo/model_doc.mustache b/modules/openapi-generator/src/main/resources/Javascript-Apollo/model_doc.mustache new file mode 100644 index 00000000000..04ab6f949ac --- /dev/null +++ b/modules/openapi-generator/src/main/resources/Javascript-Apollo/model_doc.mustache @@ -0,0 +1,26 @@ +{{#models}}{{#model}}{{#isEnum}}# {{moduleName}}.{{classname}} + +## Enum + +{{#allowableValues}}{{#enumVars}} +* `{{name}}` (value: `{{{value}}}`) +{{/enumVars}}{{/allowableValues}} +{{/isEnum}}{{^isEnum}}# {{moduleName}}.{{classname}} + +## Properties + +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +{{#vars}}**{{name}}** | {{#isPrimitiveType}}**{{dataType}}**{{/isPrimitiveType}}{{^isPrimitiveType}}[**{{dataType}}**]({{complexType}}.md){{/isPrimitiveType}} | {{description}} | {{^required}}[optional] {{/required}}{{#isReadOnly}}[readonly] {{/isReadOnly}}{{#defaultValue}}[default to {{defaultValue}}]{{/defaultValue}} +{{/vars}} +{{#vars}}{{#isEnum}} + + +## Enum: {{datatypeWithEnum}} + +{{#allowableValues}}{{#enumVars}} +* `{{name}}` (value: `{{{value}}}`) +{{/enumVars}}{{/allowableValues}} + +{{/isEnum}}{{/vars}} +{{/isEnum}}{{/model}}{{/models}} diff --git a/modules/openapi-generator/src/main/resources/Javascript-Apollo/model_test.mustache b/modules/openapi-generator/src/main/resources/Javascript-Apollo/model_test.mustache new file mode 100644 index 00000000000..9c80d3291b5 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/Javascript-Apollo/model_test.mustache @@ -0,0 +1,65 @@ +{{>licenseInfo}} +(function(root, factory) { + if (typeof define === 'function' && define.amd) { + // AMD. + define(['expect.js', process.cwd()+'/src/{{#invokerPackage}}{{invokerPackage}}/{{/invokerPackage}}index'], factory); + } else if (typeof module === 'object' && module.exports) { + // CommonJS-like environments that support module.exports, like Node. + factory(require('expect.js'), require(process.cwd()+'/src/{{#invokerPackage}}{{invokerPackage}}/{{/invokerPackage}}index')); + } else { + // Browser globals (root is window) + factory(root.expect, root.{{moduleName}}); + } +}(this, function(expect, {{moduleName}}) { + 'use strict'; + + var instance; + + beforeEach(function() { +{{#models}} +{{#model}} +{{^isEnum}} + instance = new {{moduleName}}.{{classname}}(); +{{/isEnum}} +{{/model}} +{{/models}} + }); + + var getProperty = function(object, getter, property) { + // Use getter method if present; otherwise, get the property directly. + if (typeof object[getter] === 'function') + return object[getter](); + else + return object[property]; + } + + var setProperty = function(object, setter, property, value) { + // Use setter method if present; otherwise, set the property directly. + if (typeof object[setter] === 'function') + object[setter](value); + else + object[property] = value; + } + + describe('{{classname}}', function() { + it('should create an instance of {{classname}}', function() { + // uncomment below and update the code to test {{classname}} + //var instane = new {{moduleName}}.{{classname}}(); + //expect(instance).to.be.a({{moduleName}}.{{classname}}); + }); + +{{#models}} +{{#model}} +{{#vars}} + it('should have the property {{name}} (base name: "{{baseName}}")', function() { + // uncomment below and update the code to test the property {{name}} + //var instane = new {{moduleName}}.{{classname}}(); + //expect(instance).to.be(); + }); + +{{/vars}} +{{/model}} +{{/models}} + }); + +})); diff --git a/modules/openapi-generator/src/main/resources/Javascript-Apollo/package.mustache b/modules/openapi-generator/src/main/resources/Javascript-Apollo/package.mustache new file mode 100644 index 00000000000..e6d3c752295 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/Javascript-Apollo/package.mustache @@ -0,0 +1,29 @@ +{ + "name": "{{{projectName}}}", + "version": "{{{projectVersion}}}", + "description": "{{{projectDescription}}}", + "license": "{{licenseName}}", + "main": "src/index.js", + "scripts": { + "test": "mocha --require @babel/register --recursive" + }, + "browser": { + "fs": false + }, +{{#npmRepository}} + "publishConfig":{ + "registry":"{{npmRepository}}" + }, +{{/npmRepository}} + "dependencies": { + "apollo-datasource-rest": "^0.7.0" + }, + "devDependencies": { + "expect.js": "^0.3.1", + "mocha": "^5.2.0", + "sinon": "^7.2.0" + }, + "files": [ + "src" + ] +} diff --git a/modules/openapi-generator/src/main/resources/Javascript-Apollo/partial_model_enum_class.mustache b/modules/openapi-generator/src/main/resources/Javascript-Apollo/partial_model_enum_class.mustache new file mode 100644 index 00000000000..e88ddd9cfb5 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/Javascript-Apollo/partial_model_enum_class.mustache @@ -0,0 +1,24 @@ +{{#emitJSDoc}}/** +* Enum class {{classname}}. +* @enum {{=<% %>=}}{<%datatype%>}<%={{ }}=%> +* @readonly +*/{{/emitJSDoc}} +export default class {{classname}} { + {{#allowableValues}}{{#enumVars}} + {{#emitJSDoc}}/** + * value: {{{value}}} + * @const + */{{/emitJSDoc}} + "{{name}}" = {{{value}}}; + + {{/enumVars}}{{/allowableValues}} + + {{#emitJSDoc}}/** + * Returns a {{classname}} enum value from a Javascript object name. + * @param {Object} data The plain JavaScript object containing the name of the enum value. + * @return {{=< >=}}{module:<#invokerPackage>/<#modelPackage>/}<={{ }}=> The enum {{classname}} value. + */{{/emitJSDoc}} + static constructFromObject(object) { + return object; + } +} diff --git a/modules/openapi-generator/src/main/resources/Javascript-Apollo/partial_model_generic.mustache b/modules/openapi-generator/src/main/resources/Javascript-Apollo/partial_model_generic.mustache new file mode 100644 index 00000000000..c16b43ec4d4 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/Javascript-Apollo/partial_model_generic.mustache @@ -0,0 +1,82 @@ + +{{#models}}{{#model}}{{#emitJSDoc}}/** + * The {{classname}} model module. + * @module {{#invokerPackage}}{{invokerPackage}}/{{/invokerPackage}}{{#modelPackage}}{{modelPackage}}/{{/modelPackage}}{{classname}} + * @version {{projectVersion}} + */{{/emitJSDoc}} +class {{classname}} {{#parent}}{{^parentModel}}{{#vendorExtensions.x-is-array}}extends Array {{/vendorExtensions.x-is-array}}{{/parentModel}}{{/parent}}{ + {{#vars}} + {{#emitJSDoc}}/** + * @member {{=< >=}}{<&vendorExtensions.x-jsdoc-type>}<={{ }}=> {{baseName}} + * @type {{=< >=}}{<&vendorExtensions.x-jsdoc-type>}<={{ }}=>{{#defaultValue}} + * @default {{{defaultValue}}}{{/defaultValue}} + */{{/emitJSDoc}} + {{baseName}}{{#defaultValue}} = {{{defaultValue}}}{{/defaultValue}}; + {{/vars}} + + {{#useInheritance}}{{#interfaceModels}}{{#allVars}}{{#emitJSDoc}}/** + * @member {{=< >=}}{<&vendorExtensions.x-jsdoc-type>}<={{ }}=> {{baseName}} + * @type {{=< >=}}{<&vendorExtensions.x-jsdoc-type>}<={{ }}=> + */{{/emitJSDoc}} + #{{baseName}}; + {{/allVars}}{{/interfaceModels}}{{/useInheritance}} + + {{#emitJSDoc}}/** + * Constructs a new {{classname}}.{{#description}} + * {{description}}{{/description}} + * @alias module:{{#invokerPackage}}{{invokerPackage}}/{{/invokerPackage}}{{#modelPackage}}{{modelPackage}}/{{/modelPackage}}{{classname}}{{#useInheritance}}{{#parent}} + * @extends {{#parentModel}}module:{{#invokerPackage}}{{invokerPackage}}/{{/invokerPackage}}{{#modelPackage}}{{modelPackage}}/{{/modelPackage}}{{classname}}{{/parentModel}}{{^parentModel}}{{#vendorExtensions.x-is-array}}Array{{/vendorExtensions.x-is-array}}{{#vendorExtensions.x-is-map}}Object{{/vendorExtensions.x-is-map}}{{/parentModel}}{{/parent}}{{#interfaces}} + * @implements module:{{#invokerPackage}}{{invokerPackage}}/{{/invokerPackage}}{{#modelPackage}}{{modelPackage}}/{{/modelPackage}}{{.}}{{/interfaces}}{{/useInheritance}}{{#vendorExtensions.x-all-required}} + * @param {{name}} {{=< >=}}{<&vendorExtensions.x-jsdoc-type>}<={{ }}=> {{#description}}{{{description}}}{{/description}}{{/vendorExtensions.x-all-required}} + */{{/emitJSDoc}} + constructor({{#vendorExtensions.x-all-required}}{{name}}{{^-last}}, {{/-last}}{{/vendorExtensions.x-all-required}}) { {{#parent}}{{^parentModel}}{{#vendorExtensions.x-is-array}} + super(); + {{/vendorExtensions.x-is-array}}{{/parentModel}}{{/parent}}{{#useInheritance}} + {{#interfaceModels}}{{classname}}.initialize(this{{#vendorExtensions.x-all-required}}, {{name}}{{/vendorExtensions.x-all-required}});{{/interfaceModels}}{{/useInheritance}} + {{classname}}.initialize(this{{#vendorExtensions.x-all-required}}, {{name}}{{/vendorExtensions.x-all-required}}); + } + + {{#emitJSDoc}}/** + * Initializes the fields of this object. + * This method is used by the constructors of any subclasses, in order to implement multiple inheritance (mix-ins). + * Only for internal use. + */{{/emitJSDoc}} + static initialize(obj{{#vendorExtensions.x-all-required}}, {{name}}{{/vendorExtensions.x-all-required}}) { {{#vars}}{{#required}} + obj['{{baseName}}'] = {{name}};{{/required}}{{/vars}} + } + + {{#emitJSDoc}}/** + * Constructs a {{classname}} from a plain JavaScript object, optionally creating a new instance. + * Copies all relevant properties from data to obj if supplied or a new instance if not. + * @param {Object} data The plain JavaScript object bearing properties of interest. + * @param {{=< >=}}{module:<#invokerPackage>/<#modelPackage>/}<={{ }}=> obj Optional instance to populate. + * @return {{=< >=}}{module:<#invokerPackage>/<#modelPackage>/}<={{ }}=> The populated {{classname}} instance. + */{{/emitJSDoc}} + static constructFromObject(data, obj) { + if (data){{! TODO: support polymorphism: discriminator property on data determines class to instantiate.}} { + obj = obj || new {{classname}}();{{#parent}}{{^parentModel}} + + ApiClient.constructFromObject(data, obj, '{{vendorExtensions.x-item-type}}'); + {{/parentModel}}{{/parent}}{{#useInheritance}}{{#parentModel}} + {{classname}}.constructFromObject(data, obj);{{/parentModel}}{{#interfaces}} + {{.}}.constructFromObject(data, obj);{{/interfaces}}{{/useInheritance}} + + {{#vars}} + if (data.hasOwnProperty('{{baseName}}')) { + obj['{{baseName}}']{{{defaultValueWithParam}}} + } + {{/vars}} + } + return obj; + } + {{/model}} +} + +{{#vars}}{{#isEnum}}{{^isContainer}} +{{>partial_model_inner_enum}} +{{/isContainer}}{{/isEnum}}{{#items.isEnum}}{{#items}}{{^isContainer}} +{{>partial_model_inner_enum}} +{{/isContainer}}{{/items}}{{/items.isEnum}}{{/vars}} + +export default {{classname}}; +{{/models}} diff --git a/modules/openapi-generator/src/main/resources/Javascript-Apollo/partial_model_inner_enum.mustache b/modules/openapi-generator/src/main/resources/Javascript-Apollo/partial_model_inner_enum.mustache new file mode 100644 index 00000000000..17cea4407d5 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/Javascript-Apollo/partial_model_inner_enum.mustache @@ -0,0 +1,15 @@ +{{#emitJSDoc}}/** + * Allowed values for the {{baseName}} property. + * @enum {{=<% %>=}}{<%datatype%>}<%={{ }}=%> + * @readonly + */{{/emitJSDoc}} +{{classname}}['{{datatypeWithEnum}}'] = { +{{#allowableValues}}{{#enumVars}} + {{#emitJSDoc}}/** + * value: {{{value}}} + * @const + */{{/emitJSDoc}} + "{{name}}": {{{value}}}{{^-last}}, + {{/-last}} +{{/enumVars}}{{/allowableValues}} +}; diff --git a/modules/openapi-generator/src/main/resources/Javascript-Apollo/travis.yml b/modules/openapi-generator/src/main/resources/Javascript-Apollo/travis.yml new file mode 100644 index 00000000000..0968f7a4333 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/Javascript-Apollo/travis.yml @@ -0,0 +1,5 @@ +language: node_js +cache: npm +node_js: + - "6" + - "6.1" diff --git a/modules/openapi-generator/src/main/resources/META-INF/services/org.openapitools.codegen.CodegenConfig b/modules/openapi-generator/src/main/resources/META-INF/services/org.openapitools.codegen.CodegenConfig index 58a7428cf92..cac33205e17 100644 --- a/modules/openapi-generator/src/main/resources/META-INF/services/org.openapitools.codegen.CodegenConfig +++ b/modules/openapi-generator/src/main/resources/META-INF/services/org.openapitools.codegen.CodegenConfig @@ -60,6 +60,7 @@ org.openapitools.codegen.languages.JavaResteasyServerCodegen org.openapitools.codegen.languages.JavaResteasyEapServerCodegen org.openapitools.codegen.languages.JavaJAXRSSpecServerCodegen org.openapitools.codegen.languages.JavascriptClientCodegen +org.openapitools.codegen.languages.JavascriptApolloClientCodegen org.openapitools.codegen.languages.JavascriptFlowtypedClientCodegen org.openapitools.codegen.languages.JavascriptClosureAngularClientCodegen org.openapitools.codegen.languages.JMeterClientCodegen diff --git a/pom.xml b/pom.xml index 500d195b14e..4c8f3fcb339 100644 --- a/pom.xml +++ b/pom.xml @@ -1239,6 +1239,7 @@ samples/server/petstore/php-slim samples/server/petstore/php-slim4 samples/client/petstore/javascript + samples/client/petstore/javascript-apollo samples/client/petstore/javascript-es6 samples/openapi3/client/petstore/javascript-es6 samples/client/petstore/javascript-promise diff --git a/website/i18n/en.json b/website/i18n/en.json index 890f1a9d449..89b5ad427b6 100644 --- a/website/i18n/en.json +++ b/website/i18n/en.json @@ -271,6 +271,10 @@ "title": "Config Options for javascript", "sidebar_label": "javascript" }, + "generators/javascript-apollo": { + "title": "Config Options for javascript-apollo", + "sidebar_label": "javascript-apollo" + }, "generators/jaxrs-cxf-cdi": { "title": "Config Options for jaxrs-cxf-cdi", "sidebar_label": "jaxrs-cxf-cdi"