diff --git a/README.md b/README.md
index b4a67aac55b..41bce3dc892 100644
--- a/README.md
+++ b/README.md
@@ -269,7 +269,7 @@ If you're a nix user, you can enter OpenAPI Generator shell, by typing:
```sh
nix develop
```
-It will enter a shell with Java 8 and Maven installed.
+It will enter a shell with Java 8 and Maven installed.
Direnv supports automatically loading of the nix developer shell, so if you're using direnv too, type:
```sh
@@ -1154,6 +1154,7 @@ If you want to join the committee, please kindly apply by sending an email to te
| Julia | @tanmaykm (2023/01) |
| Kotlin | @jimschubert (2017/09) [:heart:](https://www.patreon.com/jimschubert), @dr4ke616 (2018/08) @karismann (2019/03) @Zomzog (2019/04) @andrewemery (2019/10) @4brunu (2019/11) @yutaka0m (2020/03) |
| Lua | @daurnimator (2017/08) |
+| N4JS | @mmews-n4 (2023/03) |
| Nim | |
| NodeJS/Javascript | @CodeNinjai (2017/07) @frol (2017/07) @cliffano (2017/07) |
| ObjC | |
diff --git a/bin/configs/n4js-petstore.yaml b/bin/configs/n4js-petstore.yaml
new file mode 100644
index 00000000000..06ab7c93e1b
--- /dev/null
+++ b/bin/configs/n4js-petstore.yaml
@@ -0,0 +1,9 @@
+generatorName: n4js
+outputDir: samples/client/petstore/n4js
+inputSpec: modules/openapi-generator/src/test/resources/3_0/petstore.yaml
+templateDir: modules/openapi-generator/src/main/resources/n4js
+additionalProperties:
+ apiPackage: "api"
+ modelPackage: "model"
+ fetchExecuterConstName: "FETCH_EXEC"
+ fetchExecuterConstImplPath: "FetchExecuterMock"
diff --git a/docs/generators.md b/docs/generators.md
index c9b2665c4a0..276a9cb0475 100644
--- a/docs/generators.md
+++ b/docs/generators.md
@@ -44,6 +44,7 @@ The following generators are available:
* [k6 (beta)](generators/k6.md)
* [kotlin](generators/kotlin.md)
* [lua (beta)](generators/lua.md)
+* [n4js](generators/n4js.md)
* [nim (beta)](generators/nim.md)
* [objc](generators/objc.md)
* [ocaml](generators/ocaml.md)
diff --git a/docs/generators/n4js.md b/docs/generators/n4js.md
new file mode 100644
index 00000000000..5eb04776280
--- /dev/null
+++ b/docs/generators/n4js.md
@@ -0,0 +1,244 @@
+---
+title: Documentation for the n4js Generator
+---
+
+## METADATA
+
+| Property | Value | Notes |
+| -------- | ----- | ----- |
+| generator name | n4js | pass this to the generate command after -g |
+| generator stability | STABLE | |
+| generator type | CLIENT | |
+| generator language | Java | |
+| generator default templating engine | mustache | |
+| helpTxt | Generates a n4js client. | |
+
+## CONFIG OPTIONS
+These options may be applied as additional-properties (cli) or configOptions (plugins). Refer to [configuration docs](https://openapi-generator.tech/docs/configuration) for more details.
+
+| Option | Description | Values | Default |
+| ------ | ----------- | ------ | ------- |
+|apiNamePrefix|Prefix that will be appended to all API names ('tags'). Default: empty string. e.g. Pet => Pet.| |null|
+|apiPackage|package for generated api classes| |null|
+|checkRequiredParamsNotNull|Iff true null-checks are performed for required parameters.| |null|
+|checkSuperfluousBodyProps|Iff true a new copy of the given body object is transmitted. This copy only contains those properties defined in its model specification.| |null|
+|generateDefaultApiExecuter|Iff true a default implementation of the api executer interface is generated.| |null|
+|modelPackage|package for generated models| |null|
+
+## IMPORT MAPPING
+
+| Type/Alias | Imports |
+| ---------- | ------- |
+
+
+## INSTANTIATION TYPES
+
+| Type/Alias | Instantiated By |
+| ---------- | --------------- |
+
+
+## LANGUAGE PRIMITIVES
+
+
+- Array
+- Error
+- Object
+- String
+- any
+- any+
+- boolean
+- int
+- number
+- object
+- string
+
+
+## RESERVED WORDS
+
+
+- abstract
+- await
+- boolean
+- break
+- byte
+- case
+- catch
+- char
+- class
+- const
+- continue
+- debugger
+- default
+- delete
+- do
+- double
+- else
+- enum
+- export
+- extends
+- false
+- final
+- finally
+- float
+- for
+- formParams
+- function
+- goto
+- headerParams
+- if
+- implements
+- import
+- in
+- instanceof
+- int
+- interface
+- let
+- long
+- native
+- new
+- null
+- package
+- private
+- protected
+- public
+- queryParameters
+- requestOptions
+- return
+- short
+- static
+- super
+- switch
+- synchronized
+- this
+- throw
+- transient
+- true
+- try
+- typeof
+- useFormData
+- var
+- varLocalDeferred
+- varLocalPath
+- void
+- volatile
+- while
+- with
+- yield
+
+
+## FEATURE SET
+
+
+### Client Modification Feature
+| Name | Supported | Defined By |
+| ---- | --------- | ---------- |
+|BasePath|✗|ToolingExtension
+|Authorizations|✗|ToolingExtension
+|UserAgent|✗|ToolingExtension
+|MockServer|✗|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
+|Uuid|✗|
+|Array|✓|OAS2,OAS3
+|Null|✗|OAS3
+|AnyType|✗|OAS2,OAS3
+|Object|✓|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
+|allOf|✗|OAS2,OAS3
+|anyOf|✗|OAS3
+|oneOf|✗|OAS3
+|not|✗|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/N4jsClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/N4jsClientCodegen.java
new file mode 100644
index 00000000000..d099e638b57
--- /dev/null
+++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/N4jsClientCodegen.java
@@ -0,0 +1,671 @@
+package org.openapitools.codegen.languages;
+
+import static org.openapitools.codegen.CodegenConstants.API_NAME_PREFIX;
+import static org.openapitools.codegen.CodegenConstants.API_NAME_PREFIX_DESC;
+import static org.openapitools.codegen.CodegenConstants.API_PACKAGE;
+import static org.openapitools.codegen.CodegenConstants.API_PACKAGE_DESC;
+import static org.openapitools.codegen.CodegenConstants.MODEL_PACKAGE;
+import static org.openapitools.codegen.CodegenConstants.MODEL_PACKAGE_DESC;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.text.StringEscapeUtils;
+import org.openapitools.codegen.CliOption;
+import org.openapitools.codegen.CodegenConfig;
+import org.openapitools.codegen.CodegenModel;
+import org.openapitools.codegen.CodegenOperation;
+import org.openapitools.codegen.CodegenProperty;
+import org.openapitools.codegen.CodegenResponse;
+import org.openapitools.codegen.CodegenSecurity;
+import org.openapitools.codegen.CodegenType;
+import org.openapitools.codegen.DefaultCodegen;
+import org.openapitools.codegen.IJsonSchemaValidationProperties;
+import org.openapitools.codegen.SupportingFile;
+import org.openapitools.codegen.config.GlobalSettings;
+import org.openapitools.codegen.model.ModelMap;
+import org.openapitools.codegen.model.ModelsMap;
+import org.openapitools.codegen.model.OperationMap;
+import org.openapitools.codegen.model.OperationsMap;
+import org.openapitools.codegen.utils.ModelUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import io.swagger.v3.oas.models.OpenAPI;
+import io.swagger.v3.oas.models.media.ArraySchema;
+import io.swagger.v3.oas.models.media.ComposedSchema;
+import io.swagger.v3.oas.models.media.Schema;
+import io.swagger.v3.oas.models.parameters.Parameter;
+
+public class N4jsClientCodegen extends DefaultCodegen implements CodegenConfig {
+ public static final String CHECK_REQUIRED_PARAMS_NOT_NULL = "checkRequiredParamsNotNull";
+ public static final String CHECK_SUPERFLUOUS_BODY_PROPS = "checkSuperfluousBodyProps";
+ public static final String GENERATE_DEFAULT_API_EXECUTER = "generateDefaultApiExecuter";
+
+ final Logger LOGGER = LoggerFactory.getLogger(N4jsClientCodegen.class);
+
+ final Set forbiddenChars = new HashSet<>();
+
+ private boolean checkRequiredBodyPropsNotNull = true;
+ private boolean checkSuperfluousBodyProps = true;
+ private boolean generateDefaultApiExecuter = true;
+
+ public CodegenType getTag() {
+ return CodegenType.CLIENT;
+ }
+
+ public String getName() {
+ return "n4js";
+ }
+
+ public String getHelp() {
+ return "Generates a n4js client.";
+ }
+
+ public N4jsClientCodegen() {
+ super();
+
+ // disable since otherwise Modules/Class are not generated iff used as
+ // parameters only
+ GlobalSettings.setProperty("skipFormModel", "false");
+
+ specialCharReplacements.clear();
+
+ outputFolder = "generated-code" + File.separator + "n4js";
+ modelTemplateFiles.put("model.mustache", ".n4jsd");
+ apiTemplateFiles.put("api.mustache", ".n4js");
+ embeddedTemplateDir = templateDir = "n4js";
+ apiPackage = "";
+ modelPackage = "";
+
+ typeMapping = new HashMap();
+ typeMapping.put("Set", "Set");
+ typeMapping.put("set", "Set");
+ typeMapping.put("Array", "Array");
+ typeMapping.put("array", "Array");
+ typeMapping.put("boolean", "boolean");
+ typeMapping.put("string", "string");
+ typeMapping.put("char", "string");
+ typeMapping.put("float", "number");
+ typeMapping.put("long", "int");
+ typeMapping.put("short", "int");
+ typeMapping.put("int", "int");
+ typeMapping.put("integer", "int");
+ typeMapping.put("number", "number");
+ typeMapping.put("double", "number");
+ typeMapping.put("object", "object");
+ typeMapping.put("Map", "any");
+ typeMapping.put("map", "any");
+ typeMapping.put("date", "string");
+ typeMapping.put("DateTime", "string");
+ typeMapping.put("binary", "any");
+ typeMapping.put("File", "any");
+ typeMapping.put("file", "any");
+ typeMapping.put("ByteArray", "string");
+ typeMapping.put("UUID", "string");
+ typeMapping.put("URI", "string");
+ typeMapping.put("Error", "Error");
+ typeMapping.put("AnyType", "any");
+
+ importMapping.clear(); // not used
+
+ supportsInheritance = true;
+ supportsMultipleInheritance = false;
+
+ reservedWords.addAll(Arrays.asList(
+ // local variable names used in API methods (endpoints)
+ "varLocalPath", "queryParameters", "headerParams", "formParams", "useFormData", "varLocalDeferred",
+ "requestOptions",
+ // N4JS reserved words
+ "abstract", "await", "boolean", "break", "byte", "case", "catch", "char", "class", "const", "continue",
+ "debugger", "default", "delete", "do", "double", "else", "enum", "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", "transient", "true",
+ "try", "typeof", "var", "void", "volatile", "while", "with", "yield"));
+
+ languageSpecificPrimitives = new HashSet<>(Arrays.asList("string", "String", "boolean", "number", "int",
+ "Object", "object", "Array", "any", "any+", "Error"));
+
+ defaultIncludes.add("~Object+");
+ defaultIncludes.add("Object+");
+
+ forbiddenChars.add("@");
+
+ cliOptions.clear();
+ cliOptions.add(new CliOption(API_PACKAGE, API_PACKAGE_DESC));
+ cliOptions.add(new CliOption(MODEL_PACKAGE, MODEL_PACKAGE_DESC));
+ cliOptions.add(new CliOption(API_NAME_PREFIX, API_NAME_PREFIX_DESC));
+ cliOptions.add(new CliOption(CHECK_REQUIRED_PARAMS_NOT_NULL,
+ "Iff true null-checks are performed for required parameters."));
+ cliOptions.add(new CliOption(CHECK_SUPERFLUOUS_BODY_PROPS,
+ "Iff true a new copy of the given body object is transmitted. This copy only contains those properties defined in its model specification."));
+ cliOptions.add(new CliOption(GENERATE_DEFAULT_API_EXECUTER,
+ "Iff true a default implementation of the api executer interface is generated."));
+ }
+
+ @Override
+ public void processOpts() {
+ super.processOpts();
+
+ supportingFiles.clear();
+ supportingFiles.add(new SupportingFile("README.mustache", "", "README.md"));
+ supportingFiles.add(new SupportingFile("ApiHelper.mustache", apiPackage, "ApiHelper.n4js"));
+
+ checkRequiredBodyPropsNotNull = processBooleanOpt(CHECK_REQUIRED_PARAMS_NOT_NULL, checkRequiredBodyPropsNotNull);
+ checkSuperfluousBodyProps = processBooleanOpt(CHECK_SUPERFLUOUS_BODY_PROPS, checkSuperfluousBodyProps);
+ generateDefaultApiExecuter = processBooleanOpt(GENERATE_DEFAULT_API_EXECUTER, generateDefaultApiExecuter);
+
+ if (additionalProperties.get(API_PACKAGE) instanceof String) {
+ apiPackage = additionalProperties.get(API_PACKAGE).toString();
+ } else {
+ additionalProperties.put(API_PACKAGE, apiPackage);
+ }
+
+ if (additionalProperties.get(MODEL_PACKAGE) instanceof String) {
+ modelPackage = additionalProperties.get(MODEL_PACKAGE).toString();
+ } else {
+ additionalProperties.put(MODEL_PACKAGE, modelPackage);
+ }
+
+ if (additionalProperties.get(API_NAME_PREFIX) instanceof String) {
+ apiNamePrefix = additionalProperties.get(API_NAME_PREFIX).toString();
+ } else {
+ additionalProperties.put(API_NAME_PREFIX, apiNamePrefix);
+ }
+ }
+
+ private boolean processBooleanOpt(String OPT, boolean defaultValue) {
+ boolean passedValue = defaultValue;
+ if (additionalProperties.containsKey(OPT)) {
+ Object value = additionalProperties.get(OPT);
+ if (value instanceof Boolean) {
+ passedValue = (Boolean) value;
+ } else {
+ try {
+ passedValue = Boolean.parseBoolean(value.toString());
+ } catch (Exception e) {
+ // ignore
+ }
+ }
+ }
+ additionalProperties.put(OPT, passedValue);
+ return defaultValue;
+ }
+
+ @Override
+ public String toModelFilename(String name) {
+ String modelFilename = super.toModelFilename(name);
+ if (typeMapping.containsKey(modelFilename) || defaultIncludes.contains(modelFilename)) {
+ return modelFilename;
+ }
+ return modelFilename;
+ }
+
+ public boolean checkRequiredBodyPropsNotNull() {
+ return checkRequiredBodyPropsNotNull;
+ }
+
+ public boolean checkSuperfluousBodyProps() {
+ return checkSuperfluousBodyProps;
+ }
+
+ public boolean generateDefaultApiExecuter() {
+ return generateDefaultApiExecuter;
+ }
+
+ @Override
+ public boolean getUseInlineModelResolver() {
+ return false;
+ }
+
+ @Override
+ public void setOpenAPI(OpenAPI openAPI) {
+ super.setOpenAPI(openAPI);
+ typeAliases.put("object", "~Object+");
+ }
+
+ @Override
+ protected boolean isReservedWord(String word) {
+ // case sensitive matching
+ return reservedWords.contains(word);
+ }
+
+ @Override
+ public String toAnyOfName(List names, ComposedSchema composedSchema) {
+ List types = getTypesFromSchemas(composedSchema.getAnyOf());
+ return String.join(" | ", types);
+ }
+
+ @Override
+ public String toOneOfName(List names, ComposedSchema composedSchema) {
+ List types = getTypesFromSchemas(composedSchema.getOneOf());
+ return String.join(" | ", types);
+ }
+
+ @Override
+ public String toAllOfName(List names, ComposedSchema composedSchema) {
+ List types = getTypesFromSchemas(composedSchema.getAllOf());
+ return String.join(" & ", types);
+ }
+
+ /**
+ * Extracts the list of type names from a list of schemas. Excludes `AnyType` if
+ * there are other valid types extracted.
+ *
+ * @param schemas list of schemas
+ * @return list of types
+ */
+ @SuppressWarnings("rawtypes")
+ protected List getTypesFromSchemas(List schemas) {
+ List filteredSchemas = schemas.size() > 1 ? schemas.stream()
+ .filter(schema -> !"AnyType".equals(super.getSchemaType(schema))).collect(Collectors.toList())
+ : schemas;
+
+ return filteredSchemas.stream().map(schema -> getTypeDeclaration(schema)).distinct()
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ protected void addImports(Set importsToBeAddedTo, IJsonSchemaValidationProperties type) {
+ Set imports = type.getImports(importContainerType, importBaseType, generatorMetadata.getFeatureSet());
+ Set mappedImports = new HashSet<>();
+ for (String imp : imports) {
+ String mappedImp = imp;
+ if (typeMapping.containsKey(imp)) {
+ mappedImp = typeMapping.get(imp);
+ } else {
+ mappedImp = imp;
+ }
+ mappedImports.add(mappedImp);
+ }
+ addImports(importsToBeAddedTo, mappedImports);
+ }
+
+ @Override
+ protected void addImport(Set importsToBeAddedTo, String type) {
+ String[] parts = splitComposedType(type);
+ for (String s : parts) {
+ super.addImport(importsToBeAddedTo, s);
+ }
+ }
+
+ private String[] splitComposedType(String name) {
+ return name.replace(" ", "").split("[|&<>]");
+ }
+
+ @Override
+ public ModelsMap postProcessModels(ModelsMap objs) {
+ objs = super.postProcessModels(objs);
+
+ for (ModelMap modelMap : objs.getModels()) {
+ CodegenModel cgModel = modelMap.getModel();
+ if (cgModel.unescapedDescription != null && !cgModel.unescapedDescription.contains("\n * ")) {
+ cgModel.description = escapeTextWhileAllowingNewLines(cgModel.unescapedDescription.trim()).replace("\n",
+ "\n * ");
+ }
+ }
+
+ postProcessModelsEnum(objs); // enable enums
+ return objs;
+ }
+
+ @Override
+ protected void addImportsForPropertyType(CodegenModel model, CodegenProperty property) {
+ if (model.getIsAnyType()) {
+ return; // disable (unused) imports created for properties of type aliases
+ }
+ super.addImportsForPropertyType(model, property);
+ }
+
+ @Override
+ public Map postProcessAllModels(Map objs) {
+ objs = super.postProcessAllModels(objs);
+ for (String modelName : objs.keySet()) {
+ ModelsMap modelsMap = objs.get(modelName);
+
+ // imports
+ List