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 + + + +## RESERVED WORDS + + + +## 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> imports = modelsMap.getImports(); + ArrayList> n4jsImports = new ArrayList>(); + modelsMap.put("n4jsimports", n4jsImports); + String className = modelsMap.get("classname").toString(); + for (Map imp : imports) { + Map n4jsImport = toN4jsImports(className, objs, imp); + if (n4jsImport != null) { + n4jsImports.add(n4jsImport); + } + } + + // app description -> module documentation + adjustDescriptionWithNewLines(modelsMap); + } + return objs; + } + + @Override + public OperationsMap postProcessOperationsWithModels(OperationsMap operations, List allModels) { + OperationMap objs = operations.getOperations(); + + boolean needImportCleanCopyBody = false; + + // The api.mustache template requires all of the auth methods for the whole api + // Loop over all the operations and pick out each unique auth method + Map authMethodsMap = new HashMap<>(); + for (CodegenOperation op : objs.getOperation()) { + if (op.hasAuthMethods) { + for (CodegenSecurity sec : op.authMethods) { + authMethodsMap.put(sec.name, sec); + } + } + if (op.bodyParam != null && !op.bodyParam.vars.isEmpty()) { + needImportCleanCopyBody = true; + } + if (op.responses != null && op.responses.size() > 0) { + Map responses2xx = new LinkedHashMap<>(); + Map responses4xx = new LinkedHashMap<>(); + for (CodegenResponse response : op.responses) { + if (response.is2xx) { + responses2xx.put(response.baseType, response); + } + if (response.is4xx) { + responses4xx.put(response.baseType, response); + } + } + op.vendorExtensions.put("responses2xx", new ArrayList<>(responses2xx.values())); + op.vendorExtensions.put("responses4xx", new ArrayList<>(responses4xx.values())); + } + } + + operations.put("needImportCleanCopyBody", needImportCleanCopyBody); + + // If there were any auth methods specified add them to the operations context + if (!authMethodsMap.isEmpty()) { + operations.put("authMethods", authMethodsMap.values()); + operations.put("hasAuthMethods", true); + } + + // Add additional filename information for model imports in the apis + Iterator> iter = operations.getImports().iterator(); + while (iter.hasNext()) { + Map im = iter.next(); + String className = im.get("classname"); + className = convertToModelName(className); + String adjClassName = typeMapping.getOrDefault(className, className); + if (needToImport(adjClassName)) { + im.put("classname", className); + im.put("filename", toModelImport(className)); + } else { + iter.remove(); + } + } + + // app description -> module documentation + adjustDescriptionWithNewLines(additionalProperties); + + return operations; + } + + private String convertToModelName(String modelName) { + if (modelName == null) { + return modelName; + } + Schema schema = ModelUtils.getSchema(openAPI, modelName); + if (schema == null) { + return modelName; + } + if (ModelUtils.isObjectSchema(schema)) { + return toModelFilename(modelName); + } + return modelName; + } + + private void adjustDescriptionWithNewLines(Map map) { + if (map.containsKey("appDescriptionWithNewLines") + && !map.get("appDescriptionWithNewLines").toString().contains("\n * ")) { + + String appDescriptionWithNewLines = map.get("appDescriptionWithNewLines").toString(); + appDescriptionWithNewLines = appDescriptionWithNewLines.trim().replace("\n", "\n * "); + map.put("appDescriptionWithNewLines", appDescriptionWithNewLines); + } + } + + private Map toN4jsImports(String className, Map objs, Map imp) { + String modelImpName = imp.get("import"); + if (modelImpName == null) { + return null; + } + String modelName = fromModelImport(modelImpName); + if (!objs.containsKey(modelName)) { + return null; + } + ModelsMap modelsMap = objs.get(modelName); + String impClassName = modelsMap.get("classname").toString(); + if (impClassName == null || Objects.equals(impClassName, className)) { + return null; + } + Map n4jsImport = new HashMap<>(); + n4jsImport.put("elementname", impClassName); + n4jsImport.put("modulename", modelImpName); + return n4jsImport; + } + + @Override + public String toModelImport(String name) { + if ("".equals(modelPackage())) { + return name; + } else { + return modelPackage() + "/" + name; + } + } + + protected String fromModelImport(String modelImportName) { + if ("".equals(modelPackage())) { + return modelImportName; + } else if (modelImportName == null) { + return modelImportName; + } else { + if (modelImportName.startsWith(modelPackage() + "/")) { + String nameWithoutModelPackage = modelImportName.substring(1 + modelPackage().length()); + if (modelNamePrefix != null && nameWithoutModelPackage.startsWith(modelNamePrefix)) { + return nameWithoutModelPackage.substring(modelNamePrefix.length()); + } + return nameWithoutModelPackage; + } + return modelImportName; + } + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Override + public String getTypeDeclaration(Schema p) { + if (ModelUtils.isArraySchema(p)) { + Schema items = getSchemaItems((ArraySchema) p); + return getTypeDeclaration(unaliasSchema(items)) + "[]"; + } else if (ModelUtils.isMapSchema(p)) { +// Schema inner = getSchemaAdditionalProperties(p); +// return "~Object with { [key: string]: " + getTypeDeclaration(unaliasSchema(inner)) + "; }"; + return "~Object+"; + } else if (ModelUtils.isStringSchema(p)) { + if (p.getEnum() != null) { + return enumValuesToEnumTypeUnion(p.getEnum(), "string"); + } + } else if (ModelUtils.isIntegerSchema(p) || ModelUtils.isNumberSchema(p)) { + // Handle integer and double enums + if (p.getEnum() != null) { + return numericEnumValuesToEnumTypeUnion(new ArrayList(p.getEnum())); + } + } else if (ModelUtils.isFileSchema(p)) { + return "File"; + } else if (ModelUtils.isObjectSchema(p) + || ModelUtils.isObjectSchema(ModelUtils.getReferencedSchema(openAPI, p))) { + String result = super.getTypeDeclaration(p); + return toModelFilename(result); + } else if (ModelUtils.isBinarySchema(p)) { + return "ArrayBuffer"; + } + + return super.getTypeDeclaration(p); + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Override + protected String getParameterDataType(Parameter parameter, Schema p) { + // handle enums of various data types + if (ModelUtils.isArraySchema(p)) { + ArraySchema mp1 = (ArraySchema) p; + Schema inner = mp1.getItems(); + return getParameterDataType(parameter, inner) + "[]"; + } else if (ModelUtils.isMapSchema(p)) { +// Schema inner = getAdditionalProperties(p); +// return "~Object with { [key: string]: " + this.getParameterDataType(parameter, inner) + "; }"; + return "~Object+"; + } else if (ModelUtils.isStringSchema(p)) { + // Handle string enums + if (p.getEnum() != null) { + return enumValuesToEnumTypeUnion(p.getEnum(), "string"); + } + } else if (ModelUtils.isObjectSchema(p) + || ModelUtils.isObjectSchema(ModelUtils.getReferencedSchema(openAPI, p))) { + String result = super.getTypeDeclaration(p); + return toModelFilename(result); + } else if (ModelUtils.isIntegerSchema(p) || ModelUtils.isNumberSchema(p)) { + // Handle integer and double enums + if (p.getEnum() != null) { + return numericEnumValuesToEnumTypeUnion(new ArrayList(p.getEnum())); + } + } + return this.getTypeDeclaration(p); + } + + @Override + protected String getSingleSchemaType(@SuppressWarnings("rawtypes") Schema schema) { + Schema unaliasSchema = unaliasSchema(schema); + if (StringUtils.isNotBlank(unaliasSchema.get$ref())) { + String schemaName = ModelUtils.getSimpleRef(unaliasSchema.get$ref()); + if (StringUtils.isNotEmpty(schemaName)) { + if (schemaMapping.containsKey(schemaName)) { + return schemaName; + } + } + } + return super.getSingleSchemaType(unaliasSchema); + } + + /** + * Converts a list of strings to a literal union for representing enum values as + * a type. Example output: 'available' | 'pending' | 'sold' + * + * @param values list of allowed enum values + * @param dataType either "string" or "number" + * @return a literal union for representing enum values as a type + */ + private String enumValuesToEnumTypeUnion(List values, String dataType) { + StringBuilder b = new StringBuilder(); + boolean isFirst = true; + for (String value : values) { + if (!isFirst) { + b.append(" | "); + } + b.append(toEnumValue(value, dataType)); + isFirst = false; + } + return b.toString(); + } + + /** + * Converts a list of numbers to a literal union for representing enum values as + * a type. Example output: 3 | 9 | 55 + * + * @param values a list of numbers + * @return a literal union for representing enum values as a type + */ + private String numericEnumValuesToEnumTypeUnion(List values) { + List stringValues = new ArrayList<>(); + for (Number value : values) { + stringValues.add(value.toString()); + } + return enumValuesToEnumTypeUnion(stringValues, "number"); + } + + @Override + public void postProcessModelProperty(CodegenModel model, CodegenProperty property) { + if (property.unescapedDescription != null && property.unescapedDescription.contains("\n")) { + property.description = escapeTextWhileAllowingNewLines(property.unescapedDescription.trim()).replace("\n", + "\n * "); + } + } + + @Override + public String escapeText(String input) { + input = escapeTextWhileAllowingNewLines(input); + if (input == null) { + return input; + } + + // remove \n, \r + return input.replaceAll("[\\n\\r]", " "); + } + + @Override + public String escapeTextWhileAllowingNewLines(String input) { + if (input == null) { + return input; + } + + // remove \t + // outer unescape to retain the original multi-byte characters + // finally escalate characters avoiding code injection + return escapeUnsafeCharacters( + StringEscapeUtils.unescapeEcmaScript(StringEscapeUtils.escapeEcmaScript(input).replace("\\/", "/")) + .replaceAll("[\\t]", " ")); + } + + @Override + public String escapeReservedWord(String name) { + return "_" + name; + } + + @Override + public String toVarName(final String name) { + String name2 = super.toVarName(name); + for (String forbiddenChar : forbiddenChars) { + if (name2.contains(forbiddenChar)) { + return "[\"" + name2 + "\"]"; + } + } + return name2; + } + + @Override + public String toParamName(String name) { + String name2 = super.toParamName(name); + for (String forbiddenChar : forbiddenChars) { + if (name2.contains(forbiddenChar)) { + return "[\"" + name2 + "\"]"; + } + } + return name2; + } + + @Override + public String escapeQuotationMark(String input) { + // remove ', " to avoid code injection + return input.replace("\"", "").replace("'", ""); + } + + @Override + public String escapeUnsafeCharacters(String input) { + return input.replace("*/", "*_/").replace("/*", "/_*"); + } +} 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 15bc7e37b7a..4d5a896bcb4 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 @@ -81,6 +81,7 @@ org.openapitools.codegen.languages.K6ClientCodegen org.openapitools.codegen.languages.LuaClientCodegen org.openapitools.codegen.languages.MarkdownDocumentationCodegen org.openapitools.codegen.languages.MysqlSchemaCodegen +org.openapitools.codegen.languages.N4jsClientCodegen org.openapitools.codegen.languages.NimClientCodegen org.openapitools.codegen.languages.NodeJSExpressServerCodegen org.openapitools.codegen.languages.ObjcClientCodegen diff --git a/modules/openapi-generator/src/main/resources/n4js/ApiHelper.mustache b/modules/openapi-generator/src/main/resources/n4js/ApiHelper.mustache new file mode 100644 index 00000000000..698d543fffc --- /dev/null +++ b/modules/openapi-generator/src/main/resources/n4js/ApiHelper.mustache @@ -0,0 +1,119 @@ + +/** + * Implemented by client + */ +export public interface ~ApiExecuterI { + public async exec( + method: string, + path: string, + pathParams: ~Object+, + queryParams: ~Object+, + headerParams: ~Object+, + payloadContentType: string, + body: any+) : Promise>; +} + +export public interface ~ApiError { + public resultBody?: T; +} + +{{#checkRequiredParamsNotNull}} +export public function checkRequiredParams(apiName: string, params: ~Object+) : void { + for (const key of Object.keys(params)) { + const arg = params[key]; + if (arg == null) { + throw new Error('Required parameter ' + key + ' was null or undefined when calling ' + apiName + '.'); + } + } +} +{{/checkRequiredParamsNotNull}} + +{{#checkSuperfluousBodyProps}} +export public function cleanCopyBody(t : T+, ...properties: string) : ~T { + const copy : ~T+ = {}; + for (const prop in properties) { + copy[prop] = t.prop; + } + return copy; +} +{{/checkSuperfluousBodyProps}} + +{{#generateDefaultApiExecuter}} +/** + * Default implementation of ApiExecuterI + * + * The following dependencies are necessary: + * - n4js-runtime-esnext + * - n4js-runtime-es2015 + * - n4js-runtime-html5 + */ +export public class FetchApiExec implements ApiExecuterI { + public apiOrigin: string; + const jsonTypes = ["application/json", "application/problem+json"]; + + @Override + public async exec( + method: string, + path: string, + pathParams: ~Object+, + queryParams: ~Object+, + headerParams: ~Object+, + payloadContentType: string, + body: any+ + ): Promise> { + + if (pathParams) { + for (const [k, v] of Object.entries(pathParams)) { + path = path.replace(`{${k}}`, encodeURIComponent(String(v))); + } + } + const query: string[] = []; + if (queryParams) { + for (const [k, v] of Object.entries(queryParams)) { + query.push(`${k}=${encodeURIComponent(String(v))}`); + } + } + + let url = `${this.apiOrigin}${path}`; + if (query.length) { + url += `?${query.join("&")}`; + } + + const headers: Object+ = {}; + if (payloadContentType) { + headers["content-type"] = payloadContentType; + if (this.constructor.jsonTypes.includes(payloadContentType)) { + body = JSON.stringify(body); + } + } + Object.assign(headers, headerParams); + + return await this.fetchExec(url, { + method, + headers, + body, + }); + } + + protected async fetchExec(url: string, reqInit: RequestInit): Promise> { + const resp = await fetch(url, reqInit); + + if (resp.status !== 204) { + const contentType = (resp.headers.get("content-type") || "").split(";")[0]; + const body = this.constructor.jsonTypes.includes(contentType) + ? await resp.json() + : await resp.text(); + + if (!resp.ok) { + await this.handleError(resp, body); + } + return body as R; + } + return null; + } + + protected async handleError(resp: Response, body): Promise> { + throw {body: body}; + } +} +{{/generateDefaultApiExecuter}} diff --git a/modules/openapi-generator/src/main/resources/n4js/README.mustache b/modules/openapi-generator/src/main/resources/n4js/README.mustache new file mode 100644 index 00000000000..9a177e0e4fb --- /dev/null +++ b/modules/openapi-generator/src/main/resources/n4js/README.mustache @@ -0,0 +1,55 @@ +# Documentation for {{appName}} + +- API version: {{appVersion}} +{{^hideGenerationTimestamp}} + +- Build date: {{generatedDate}} +{{/hideGenerationTimestamp}} + +{{{appDescriptionWithNewLines}}} + +{{#infoUrl}} + For more information, please visit [{{{infoUrl}}}]({{{infoUrl}}}) +{{/infoUrl}} + +*Automatically generated by the [OpenAPI Generator](https://openapi-generator.tech)* + + +## Getting started + +Configure the following elements: +- In open-api-n4js.yaml please add under 'additionalProperties': + - property 'fetchExecuterConstName' + - property 'fetchExecuterConstImplPath' +- The generated output directory needs to be augmented with an implementing n4js file + +## Example + +**open-api-n4js.yaml** +```yaml + generatorName: n4js + outputDir: /working_dir/gen-n4js/ + inputSpec: /working_dir/api-spec/main.yaml + templateDir: /openapi-generator/modules/openapi-generator/src/main/resources/n4js + additionalProperties: + fetchExecuterConstName: "FETCH_EXEC" + fetchExecuterConstImplPath: "FetchExecuterImpl" +``` + +**FetchExecuterImpl.n4js** +```typescript + import {FetchExecuterI} from "api/ApiHelper"; + + export public const FETCH_EXEC = new FetchExecuterMock(); + + export public class FetchExecuterMock implements FetchExecuterI { + @Override + public async run( + path: string, + query: ~Object=, + reqInit: ~Object= {}): ~Object with {get status() : number, json(): Promise} { + + return null; + } + } +``` diff --git a/modules/openapi-generator/src/main/resources/n4js/api.mustache b/modules/openapi-generator/src/main/resources/n4js/api.mustache new file mode 100644 index 00000000000..d48b57d3af5 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/n4js/api.mustache @@ -0,0 +1,53 @@ +{{>licenseInfo}} + +import { ApiExecuterI, ApiError{{#checkRequiredParamsNotNull}}, checkRequiredParams{{/checkRequiredParamsNotNull}}{{#checkSuperfluousBodyProps}}{{#needImportCleanCopyBody}}, cleanCopyBody{{/needImportCleanCopyBody}}{{/checkSuperfluousBodyProps}} } from "{{{apiPackage}}}/ApiHelper" +{{#imports}} +import { {{classname}} } from '{{filename}}'; +{{/imports}} + + +{{#operations}} +{{#description}} +/* + * {{&description}} + */ +{{/description}} + +{{#operation}} + +/** + {{#notes}} + * {{¬es}} + {{/notes}} + {{#summary}} + * @summary {{&summary}} + {{/summary}} + * @param fe Callback interface that runs the fetch query + {{#allParams}} + * @param {{paramName}} {{description}} + {{/allParams}} + {{#responses}} + * @response {{code}} [{{#dataType}}{{.}}{{/dataType}}{{^dataType}}undefined{{/dataType}}] {{message}} + {{/responses}} + */ +export public async function {{{classname}}}__{{{nickname}}}(fe : ApiExecuterI, {{#allParams}}{{{paramName}}}: {{{dataType}}}{{^required}}={{/required}}{{^-last}}, {{/-last}}{{/allParams}}) : Promise<{{#returnType}}{{.}}{{/returnType}}{{^returnType}}undefined{{/returnType}}, Object{{#vendorExtensions}}{{#responses4xx.0}} | ApiError<{{#responses4xx}}{{{baseType}}}{{^-last}} | {{/-last}}{{/responses4xx}}>{{/responses4xx.0}}{{/vendorExtensions}}> { +{{#checkRequiredParamsNotNull}} checkRequiredParams('{{{nickname}}}', { {{#allParams}}{{#required}}'{{{paramName}}}': {{{paramName}}}{{^-last}}, {{/-last}}{{/required}}{{/allParams}} }); + +{{/checkRequiredParamsNotNull}} + const _pathParams = { {{#pathParams}} + '{{baseName}}': {{{paramName}}}{{^-last}},{{/-last}} {{/pathParams}}}; + const _queryParams = { {{#queryParams}} + '{{baseName}}': {{{paramName}}}{{^-last}},{{/-last}} {{/queryParams}}}; + const _headerParams = { {{#headerParams}} + '{{baseName}}': {{{paramName}}}{{^-last}},{{/-last}} {{/headerParams}}}; + const _body = {{^bodyParam}}undefined{{/bodyParam}}{{#bodyParam}}{{#vars.empty}}{{{paramName}}}{{/vars.empty}}{{^vars.empty}}{{#checkSuperfluousBodyProps}}cleanCopyBody({{{paramName}}}{{#vars}}, '{{{baseName}}}'{{/vars}}){{/checkSuperfluousBodyProps}}{{^checkSuperfluousBodyProps}}{{{paramName}}}{{/checkSuperfluousBodyProps}}{{/vars.empty}}{{/bodyParam}}; + + {{#returnType}}return {{/returnType}}await fe.{{#returnType}}<{{.}}, {{#vendorExtensions}}{{^responses4xx.0}}undefined{{/responses4xx.0}}{{#responses4xx.0}}{{#responses4xx}}{{{baseType}}}{{^-last}} | {{/-last}}{{/responses4xx}}{{/responses4xx.0}}{{/vendorExtensions}}>{{/returnType}}exec( + '{{httpMethod}}', '{{{basePathWithoutHost}}}' + '{{{path}}}', + _pathParams, _queryParams, _headerParams, + {{#responses2xx.0}}'{{{mediaType}}}'{{/responses2xx.0}}{{^responses2xx.0}}undefined{{/responses2xx.0}}, + _body + ); +} +{{/operation}} +{{/operations}} \ No newline at end of file diff --git a/modules/openapi-generator/src/main/resources/n4js/licenseInfo.mustache b/modules/openapi-generator/src/main/resources/n4js/licenseInfo.mustache new file mode 100644 index 00000000000..2864dc5adf5 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/n4js/licenseInfo.mustache @@ -0,0 +1,11 @@ +/* + * {{{appName}}} + * {{{appDescriptionWithNewLines}}} + * + * {{#version}}The version of the OpenAPI document: {{{.}}}{{/version}} + * {{#infoEmail}}Contact: {{{.}}}{{/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/n4js/model.mustache b/modules/openapi-generator/src/main/resources/n4js/model.mustache new file mode 100644 index 00000000000..3bf06e10841 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/n4js/model.mustache @@ -0,0 +1,45 @@ +{{>licenseInfo}} +{{#models}} +{{#model}} +{{#n4jsimports}} +import { {{{elementname}}} } from '{{modulename}}'; +{{/n4jsimports}} + +{{#description}} +/** + * {{{.}}} + */ +{{/description}} +{{#isModel}} +export external public interface ~{{{classname}}} {{#parent}}extends {{{.}}} {{/parent}}{ +{{#vars}} +{{#description}} + + /** + * {{{.}}} + */ +{{/description}} + public {{{name}}}{{^required}}?{{/required}}: {{{dataType}}}; +{{/vars}} +} +{{/isModel}} +{{#isAnyType}} +export external public type {{{classname}}} = {{{dataType}}}; +{{/isAnyType}} +{{#isEnum}} +{{#isString}} +@StringBased +{{/isString}} +{{#isNumber}} +@NumberBased +{{/isNumber}} +export external public enum {{{classname}}} { + {{#allowableValues}} + {{#enumVars}} + {{{name}}}: {{{value}}}{{^-last}},{{/-last}} + {{/enumVars}} + {{/allowableValues}} +} +{{/isEnum}} +{{/model}} +{{/models}} \ No newline at end of file diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/n4js/N4jsClientCodegenOptionsTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/n4js/N4jsClientCodegenOptionsTest.java new file mode 100644 index 00000000000..58abdd8a985 --- /dev/null +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/n4js/N4jsClientCodegenOptionsTest.java @@ -0,0 +1,46 @@ +package org.openapitools.codegen.n4js; + +import static java.lang.Boolean.parseBoolean; +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.openapitools.codegen.CodegenConstants.API_NAME_PREFIX; +import static org.openapitools.codegen.CodegenConstants.API_PACKAGE; +import static org.openapitools.codegen.CodegenConstants.MODEL_PACKAGE; +import static org.openapitools.codegen.languages.N4jsClientCodegen.CHECK_REQUIRED_PARAMS_NOT_NULL; +import static org.openapitools.codegen.languages.N4jsClientCodegen.CHECK_SUPERFLUOUS_BODY_PROPS; +import static org.openapitools.codegen.languages.N4jsClientCodegen.GENERATE_DEFAULT_API_EXECUTER; +import static org.openapitools.codegen.options.N4jsClientCodegenOptionsProvider.CHECK_REQUIRED_PARAMS_NOT_NULL__VALUE; +import static org.openapitools.codegen.options.N4jsClientCodegenOptionsProvider.CHECK_SUPERFLUOUS_BODY_PROPS__VALUE; +import static org.openapitools.codegen.options.N4jsClientCodegenOptionsProvider.GENERATE_DEFAULT_API_EXECUTER__VALUE; + +import org.openapitools.codegen.AbstractOptionsTest; +import org.openapitools.codegen.CodegenConfig; +import org.openapitools.codegen.languages.N4jsClientCodegen; +import org.openapitools.codegen.options.N4jsClientCodegenOptionsProvider; + +public class N4jsClientCodegenOptionsTest extends AbstractOptionsTest { + private N4jsClientCodegen codegen = mock(N4jsClientCodegen.class, mockSettings); + + public N4jsClientCodegenOptionsTest() { + super(new N4jsClientCodegenOptionsProvider()); + } + + @Override + protected CodegenConfig getCodegenConfig() { + return codegen; + } + + @Override + protected void verifyOptions() { + assertEquals(parseBoolean(CHECK_REQUIRED_PARAMS_NOT_NULL__VALUE), + codegen.additionalProperties().get(CHECK_REQUIRED_PARAMS_NOT_NULL)); + assertEquals(parseBoolean(CHECK_SUPERFLUOUS_BODY_PROPS__VALUE), + codegen.additionalProperties().get(CHECK_SUPERFLUOUS_BODY_PROPS)); + assertEquals(parseBoolean(GENERATE_DEFAULT_API_EXECUTER__VALUE), + codegen.additionalProperties().get(GENERATE_DEFAULT_API_EXECUTER)); + + assertEquals("", codegen.additionalProperties().get(API_PACKAGE)); + assertEquals("", codegen.additionalProperties().get(MODEL_PACKAGE)); + assertEquals("", codegen.additionalProperties().get(API_NAME_PREFIX)); + } +} diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/n4js/N4jsClientCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/n4js/N4jsClientCodegenTest.java new file mode 100644 index 00000000000..9ab875232f9 --- /dev/null +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/n4js/N4jsClientCodegenTest.java @@ -0,0 +1,1893 @@ +package org.openapitools.codegen.n4js; + +import static org.openapitools.codegen.TestUtils.validateJavaSourceFiles; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; +import static org.testng.Assert.fail; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Optional; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import org.openapitools.codegen.ClientOptInput; +import org.openapitools.codegen.CodegenConstants; +import org.openapitools.codegen.CodegenModel; +import org.openapitools.codegen.CodegenOperation; +import org.openapitools.codegen.CodegenParameter; +import org.openapitools.codegen.CodegenProperty; +import org.openapitools.codegen.CodegenResponse; +import org.openapitools.codegen.CodegenSecurity; +import org.openapitools.codegen.DefaultCodegen; +import org.openapitools.codegen.DefaultGenerator; +import org.openapitools.codegen.TestUtils; +import org.openapitools.codegen.config.CodegenConfigurator; +import org.openapitools.codegen.java.assertions.JavaFileAssert; +import org.openapitools.codegen.languages.AbstractJavaCodegen; +import org.openapitools.codegen.languages.JavaClientCodegen; +import org.openapitools.codegen.languages.N4jsClientCodegen; +import org.openapitools.codegen.languages.TypeScriptClientCodegen; +import org.openapitools.codegen.languages.features.BeanValidationFeatures; +import org.openapitools.codegen.languages.features.CXFServerFeatures; +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.testng.Assert; +import org.testng.annotations.Ignore; +import org.testng.annotations.Test; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Sets; + +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.Operation; +import io.swagger.v3.oas.models.PathItem; +import io.swagger.v3.oas.models.media.ArraySchema; +import io.swagger.v3.oas.models.media.ComposedSchema; +import io.swagger.v3.oas.models.media.Content; +import io.swagger.v3.oas.models.media.IntegerSchema; +import io.swagger.v3.oas.models.media.MapSchema; +import io.swagger.v3.oas.models.media.MediaType; +import io.swagger.v3.oas.models.media.ObjectSchema; +import io.swagger.v3.oas.models.media.Schema; +import io.swagger.v3.oas.models.media.StringSchema; +import io.swagger.v3.oas.models.parameters.RequestBody; +import io.swagger.v3.oas.models.responses.ApiResponse; +import io.swagger.v3.parser.util.SchemaTypeUtil; + +@SuppressWarnings("deprecation") +public class N4jsClientCodegenTest { + + N4jsClientCodegen codegen = new N4jsClientCodegen(); + + @Test + public void getTypeDeclarationTest() { + Schema childSchema = new ArraySchema().items(new StringSchema()); + + OpenAPI api = TestUtils.createOpenAPI(); + api.getComponents().addSchemas("Child", childSchema); + + TypeScriptClientCodegen codegen = new TypeScriptClientCodegen(); + codegen.setOpenAPI(api); + + // Cf. issue #4968: Array of Alias of Array + Schema parentSchema = new ArraySchema().items( + new Schema<>().$ref("#/components/schemas/Child") + ); + + ModelUtils.setGenerateAliasAsModel(false); + Assert.assertEquals(codegen.getTypeDeclaration(parentSchema), "Array>"); + + ModelUtils.setGenerateAliasAsModel(true); + Assert.assertEquals(codegen.getTypeDeclaration(parentSchema), "Array"); + + // Same for Map + parentSchema = new MapSchema().additionalProperties(new Schema<>().$ref("#/components/schemas/Child")); + + ModelUtils.setGenerateAliasAsModel(false); + Assert.assertEquals(codegen.getTypeDeclaration(parentSchema), "{ [key: string]: Array; }"); + + ModelUtils.setGenerateAliasAsModel(true); + Assert.assertEquals(codegen.getTypeDeclaration(parentSchema), "{ [key: string]: Child; }"); + } + + @Test + public void testComposedSchemasImportTypesIndividually() { + final TypeScriptClientCodegen codegen = new TypeScriptClientCodegen(); + final OpenAPI openApi = TestUtils.parseFlattenSpec("src/test/resources/3_0/composed-schemas.yaml"); + codegen.setOpenAPI(openApi); + PathItem path = openApi.getPaths().get("/pets"); + CodegenOperation operation = codegen.fromOperation("/pets", "patch", path.getPatch(), path.getServers()); + // TODO revise the commented test below as oneOf is no longer defined inline + //but instead defined using $ref with the new inline model resolver in 6.x + //Assert.assertEquals(operation.imports, Sets.newHashSet("Cat", "Dog")); + Assert.assertEquals(operation.imports, Sets.newHashSet("PetsPatchRequest")); + + } + + @Test + public void testArrayWithUniqueItems() { + final Schema uniqueArray = new ArraySchema() + .items(new StringSchema()) + .uniqueItems(true); + final Schema model = new ObjectSchema() + .description("an object has an array with uniqueItems") + .addProperties("uniqueArray", uniqueArray) + .addRequiredItem("uniqueArray"); + + final DefaultCodegen codegen = new TypeScriptClientCodegen(); + final OpenAPI openAPI = TestUtils.createOpenAPIWithOneSchema("sample", model); + codegen.setOpenAPI(openAPI); + + final CodegenModel codegenModel = codegen.fromModel("sample", model); + + Assert.assertFalse(codegenModel.imports.contains("Set")); + } + + @Test + public void testWithAdditionalProperties() { + final Schema inner = new ObjectSchema(); + inner.setAdditionalProperties(true); + + final Schema root = new ObjectSchema() + .addProperties("inner", inner); + + final DefaultCodegen codegen = new TypeScriptClientCodegen(); + final OpenAPI openAPI = TestUtils.createOpenAPIWithOneSchema("sample", root); + codegen.setOpenAPI(openAPI); + + try { + // TypeScriptClientCodegen can generate codes without throwing exception. + codegen.fromModel("sample", root); + } catch (Exception e) { + Assert.fail("Exception was thrown."); + } + } + + @Test + public void defaultModelImportTest() { + final DefaultCodegen codegen = new TypeScriptClientCodegen(); + + final CodegenModel cm = new CodegenModel(); + cm.setImports(Collections.singleton("ApiResponse")); + final ModelsMap models = new ModelsMap(); + final ModelMap model = new ModelMap(); + model.setModel(cm); + models.setModels(Collections.singletonList(model)); + + final ModelsMap processedModels = codegen.postProcessModels(models); + @SuppressWarnings("unchecked") + final List> tsImports = (List>) processedModels.getModels().get(0).get("tsImports"); + Assert.assertEquals(tsImports.get(0).get("filename"), "../models/ApiResponse".replace("/", File.separator)); + Assert.assertEquals(tsImports.get(0).get("classname"), "ApiResponse"); + } + + @Test + public void modelImportWithMappingTest() { + final DefaultCodegen codegen = new TypeScriptClientCodegen(); + final String mappedName = "@namespace/dir/response"; + codegen.importMapping().put("ApiResponse", mappedName); + + final CodegenModel cm = new CodegenModel(); + cm.setImports(Collections.singleton("ApiResponse")); + final ModelsMap models = new ModelsMap(); + final ModelMap model = new ModelMap(); + model.setModel(cm); + models.setModels(Collections.singletonList(model)); + + final ModelsMap processedModels = codegen.postProcessModels(models); + @SuppressWarnings("unchecked") + final List> tsImports = (List>) processedModels.getModels().get(0).get("tsImports"); + Assert.assertEquals(tsImports.get(0).get("filename"), mappedName); + Assert.assertEquals(tsImports.get(0).get("classname"), "ApiResponse"); + } + + @Test + public void testCompilePattern() { + final DefaultCodegen codegen = new TypeScriptClientCodegen(); + final StringSchema prop = new StringSchema(); + prop.setPattern("[A-Z]{3}"); + final Schema root = new ObjectSchema().addProperty("stringPattern", prop); + final OpenAPI openApi = TestUtils.createOpenAPIWithOneSchema("sample", root); + codegen.setOpenAPI(openApi); + + try { + final CodegenModel model = codegen.fromModel("sample", root); + Assert.assertEquals(model.getAllVars().get(0).getPattern(), "/[A-Z]{3}/"); + } catch (Exception ex) { + Assert.fail("Exception was thrown."); + } + } + + @Test + public void arraysInRequestBody() { + OpenAPI openAPI = TestUtils.createOpenAPI(); + final JavaClientCodegen codegen = new JavaClientCodegen(); + codegen.setOpenAPI(openAPI); + + RequestBody body1 = new RequestBody(); + body1.setDescription("A list of ids"); + body1.setContent(new Content().addMediaType("application/json", new MediaType().schema(new ArraySchema().items(new StringSchema())))); + CodegenParameter codegenParameter1 = codegen.fromRequestBody(body1, new HashSet(), null); + Assert.assertEquals(codegenParameter1.description, "A list of ids"); + Assert.assertEquals(codegenParameter1.dataType, "List"); + Assert.assertEquals(codegenParameter1.baseType, "String"); + + RequestBody body2 = new RequestBody(); + body2.setDescription("A list of list of values"); + body2.setContent(new Content().addMediaType("application/json", new MediaType().schema(new ArraySchema().items(new ArraySchema().items(new IntegerSchema()))))); + CodegenParameter codegenParameter2 = codegen.fromRequestBody(body2, new HashSet(), null); + Assert.assertEquals(codegenParameter2.description, "A list of list of values"); + Assert.assertEquals(codegenParameter2.dataType, "List>"); + Assert.assertEquals(codegenParameter2.baseType, "List"); + + RequestBody body3 = new RequestBody(); + body3.setDescription("A list of points"); + body3.setContent(new Content().addMediaType("application/json", new MediaType().schema(new ArraySchema().items(new ObjectSchema().$ref("#/components/schemas/Point"))))); + ObjectSchema point = new ObjectSchema(); + point.addProperties("message", new StringSchema()); + point.addProperties("x", new IntegerSchema().format(SchemaTypeUtil.INTEGER32_FORMAT)); + point.addProperties("y", new IntegerSchema().format(SchemaTypeUtil.INTEGER32_FORMAT)); + CodegenParameter codegenParameter3 = codegen.fromRequestBody(body3, new HashSet(), null); + Assert.assertEquals(codegenParameter3.description, "A list of points"); + Assert.assertEquals(codegenParameter3.dataType, "List"); + Assert.assertEquals(codegenParameter3.baseType, "Point"); + } + + @Test + public void nullValuesInComposedSchema() { + final JavaClientCodegen codegen = new JavaClientCodegen(); + ComposedSchema schema = new ComposedSchema(); + CodegenModel result = codegen.fromModel("CompSche", + schema); + Assert.assertEquals(result.name, "CompSche"); + } + + @Test + public void testParametersAreCorrectlyOrderedWhenUsingRetrofit() { + JavaClientCodegen javaClientCodegen = new JavaClientCodegen(); + javaClientCodegen.setLibrary(JavaClientCodegen.RETROFIT_2); + + CodegenOperation codegenOperation = new CodegenOperation(); + CodegenParameter queryParamRequired = createQueryParam("queryParam1", true); + CodegenParameter queryParamOptional = createQueryParam("queryParam2", false); + CodegenParameter pathParam1 = createPathParam("pathParam1"); + CodegenParameter pathParam2 = createPathParam("pathParam2"); + + codegenOperation.allParams.addAll(Arrays.asList(queryParamRequired, pathParam1, pathParam2, queryParamOptional)); + OperationMap operations = new OperationMap(); + operations.setOperation(codegenOperation); + + OperationsMap objs = new OperationsMap(); + objs.setOperation(operations); + objs.setImports(new ArrayList<>()); + + javaClientCodegen.postProcessOperationsWithModels(objs, Collections.emptyList()); + + Assert.assertEquals(Arrays.asList(pathParam1, pathParam2, queryParamRequired, queryParamOptional), codegenOperation.allParams); + } + + @Test + public void testInitialConfigValues() throws Exception { + final JavaClientCodegen codegen = new JavaClientCodegen(); + codegen.processOpts(); + + Assert.assertEquals(codegen.additionalProperties().get(CodegenConstants.HIDE_GENERATION_TIMESTAMP), Boolean.FALSE); + Assert.assertFalse(codegen.isHideGenerationTimestamp()); + + Assert.assertEquals(codegen.modelPackage(), "org.openapitools.client.model"); + Assert.assertEquals(codegen.additionalProperties().get(CodegenConstants.MODEL_PACKAGE), "org.openapitools.client.model"); + Assert.assertEquals(codegen.apiPackage(), "org.openapitools.client.api"); + Assert.assertEquals(codegen.additionalProperties().get(CodegenConstants.API_PACKAGE), "org.openapitools.client.api"); + Assert.assertEquals(codegen.getInvokerPackage(), "org.openapitools.client"); + Assert.assertEquals(codegen.additionalProperties().get(CodegenConstants.INVOKER_PACKAGE), "org.openapitools.client"); + Assert.assertEquals(codegen.getSerializationLibrary(), JavaClientCodegen.SERIALIZATION_LIBRARY_GSON); + } + + @Test + public void testSettersForConfigValues() throws Exception { + final JavaClientCodegen codegen = new JavaClientCodegen(); + codegen.setHideGenerationTimestamp(true); + codegen.setModelPackage("xyz.yyyyy.zzzzzzz.model"); + codegen.setApiPackage("xyz.yyyyy.zzzzzzz.api"); + codegen.setInvokerPackage("xyz.yyyyy.zzzzzzz.invoker"); + codegen.setSerializationLibrary("JACKSON"); + codegen.processOpts(); + + Assert.assertEquals(codegen.additionalProperties().get(CodegenConstants.HIDE_GENERATION_TIMESTAMP), Boolean.TRUE); + Assert.assertTrue(codegen.isHideGenerationTimestamp()); + Assert.assertEquals(codegen.modelPackage(), "xyz.yyyyy.zzzzzzz.model"); + Assert.assertEquals(codegen.additionalProperties().get(CodegenConstants.MODEL_PACKAGE), "xyz.yyyyy.zzzzzzz.model"); + Assert.assertEquals(codegen.apiPackage(), "xyz.yyyyy.zzzzzzz.api"); + Assert.assertEquals(codegen.additionalProperties().get(CodegenConstants.API_PACKAGE), "xyz.yyyyy.zzzzzzz.api"); + Assert.assertEquals(codegen.getInvokerPackage(), "xyz.yyyyy.zzzzzzz.invoker"); + Assert.assertEquals(codegen.additionalProperties().get(CodegenConstants.INVOKER_PACKAGE), "xyz.yyyyy.zzzzzzz.invoker"); + Assert.assertEquals(codegen.getSerializationLibrary(), JavaClientCodegen.SERIALIZATION_LIBRARY_GSON); // the library JavaClientCodegen.OKHTTP_GSON only supports GSON + } + + @Test + public void testAdditionalPropertiesPutForConfigValues() throws Exception { + final JavaClientCodegen codegen = new JavaClientCodegen(); + codegen.additionalProperties().put(CodegenConstants.HIDE_GENERATION_TIMESTAMP, "true"); + codegen.additionalProperties().put(CodegenConstants.MODEL_PACKAGE, "xyz.yyyyy.zzzzzzz.mmmmm.model"); + codegen.additionalProperties().put(CodegenConstants.API_PACKAGE, "xyz.yyyyy.zzzzzzz.aaaaa.api"); + codegen.additionalProperties().put(CodegenConstants.INVOKER_PACKAGE, "xyz.yyyyy.zzzzzzz.iiii.invoker"); + codegen.additionalProperties().put(CodegenConstants.SERIALIZATION_LIBRARY, "JACKSON"); + codegen.additionalProperties().put(CodegenConstants.LIBRARY, JavaClientCodegen.JERSEY2); + codegen.processOpts(); + + Assert.assertEquals(codegen.additionalProperties().get(CodegenConstants.HIDE_GENERATION_TIMESTAMP), Boolean.TRUE); + Assert.assertTrue(codegen.isHideGenerationTimestamp()); + Assert.assertEquals(codegen.modelPackage(), "xyz.yyyyy.zzzzzzz.mmmmm.model"); + Assert.assertEquals(codegen.additionalProperties().get(CodegenConstants.MODEL_PACKAGE), "xyz.yyyyy.zzzzzzz.mmmmm.model"); + Assert.assertEquals(codegen.apiPackage(), "xyz.yyyyy.zzzzzzz.aaaaa.api"); + Assert.assertEquals(codegen.additionalProperties().get(CodegenConstants.API_PACKAGE), "xyz.yyyyy.zzzzzzz.aaaaa.api"); + Assert.assertEquals(codegen.getInvokerPackage(), "xyz.yyyyy.zzzzzzz.iiii.invoker"); + Assert.assertEquals(codegen.additionalProperties().get(CodegenConstants.INVOKER_PACKAGE), "xyz.yyyyy.zzzzzzz.iiii.invoker"); + Assert.assertEquals(codegen.getSerializationLibrary(), JavaClientCodegen.SERIALIZATION_LIBRARY_JACKSON); + } + + @Test + public void testPackageNamesSetInvokerDerivedFromApi() { + final JavaClientCodegen codegen = new JavaClientCodegen(); + codegen.additionalProperties().put(CodegenConstants.MODEL_PACKAGE, "xyz.yyyyy.zzzzzzz.mmmmm.model"); + codegen.additionalProperties().put(CodegenConstants.API_PACKAGE, "xyz.yyyyy.zzzzzzz.aaaaa.api"); + codegen.processOpts(); + + Assert.assertEquals(codegen.modelPackage(), "xyz.yyyyy.zzzzzzz.mmmmm.model"); + Assert.assertEquals(codegen.additionalProperties().get(CodegenConstants.MODEL_PACKAGE), "xyz.yyyyy.zzzzzzz.mmmmm.model"); + Assert.assertEquals(codegen.apiPackage(), "xyz.yyyyy.zzzzzzz.aaaaa.api"); + Assert.assertEquals(codegen.additionalProperties().get(CodegenConstants.API_PACKAGE), "xyz.yyyyy.zzzzzzz.aaaaa.api"); + Assert.assertEquals(codegen.getInvokerPackage(), "xyz.yyyyy.zzzzzzz.aaaaa"); + Assert.assertEquals(codegen.additionalProperties().get(CodegenConstants.INVOKER_PACKAGE), "xyz.yyyyy.zzzzzzz.aaaaa"); + } + + @Test + public void testPackageNamesSetInvokerDerivedFromModel() { + final JavaClientCodegen codegen = new JavaClientCodegen(); + codegen.additionalProperties().put(CodegenConstants.MODEL_PACKAGE, "xyz.yyyyy.zzzzzzz.mmmmm.model"); + codegen.processOpts(); + + Assert.assertEquals(codegen.modelPackage(), "xyz.yyyyy.zzzzzzz.mmmmm.model"); + Assert.assertEquals(codegen.additionalProperties().get(CodegenConstants.MODEL_PACKAGE), "xyz.yyyyy.zzzzzzz.mmmmm.model"); + Assert.assertEquals(codegen.apiPackage(), "org.openapitools.client.api"); + Assert.assertEquals(codegen.additionalProperties().get(CodegenConstants.API_PACKAGE), "org.openapitools.client.api"); + Assert.assertEquals(codegen.getInvokerPackage(), "xyz.yyyyy.zzzzzzz.mmmmm"); + Assert.assertEquals(codegen.additionalProperties().get(CodegenConstants.INVOKER_PACKAGE), "xyz.yyyyy.zzzzzzz.mmmmm"); + } + + @Test + public void testGetSchemaTypeWithComposedSchemaWithAllOf() { + final OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/2_0/composed-allof.yaml"); + final JavaClientCodegen codegen = new JavaClientCodegen(); + + Operation operation = openAPI.getPaths().get("/ping").getPost(); + CodegenOperation co = codegen.fromOperation("/ping", "POST", operation, null); + Assert.assertEquals(co.allParams.size(), 1); + Assert.assertEquals(co.allParams.get(0).baseType, "MessageEventCoreWithTimeListEntries"); + } + + @Test + public void updateCodegenPropertyEnum() { + final JavaClientCodegen codegen = new JavaClientCodegen(); + CodegenProperty array = codegenPropertyWithArrayOfIntegerValues(); + + codegen.updateCodegenPropertyEnum(array); + + @SuppressWarnings("unchecked") + List> enumVars = (List>) array.getItems().getAllowableValues().get("enumVars"); + Assert.assertNotNull(enumVars); + Map testedEnumVar = enumVars.get(0); + Assert.assertNotNull(testedEnumVar); + Assert.assertEquals(testedEnumVar.getOrDefault("name", ""), "NUMBER_1"); + Assert.assertEquals(testedEnumVar.getOrDefault("value", ""), "1"); + } + + @Test + public void updateCodegenPropertyEnumWithCustomNames() { + final JavaClientCodegen codegen = new JavaClientCodegen(); + CodegenProperty array = codegenPropertyWithArrayOfIntegerValues(); + array.getItems().setVendorExtensions(Collections.singletonMap("x-enum-varnames", Collections.singletonList("ONE"))); + + codegen.updateCodegenPropertyEnum(array); + + @SuppressWarnings("unchecked") + List> enumVars = (List>) array.getItems().getAllowableValues().get("enumVars"); + Assert.assertNotNull(enumVars); + Map testedEnumVar = enumVars.get(0); + Assert.assertNotNull(testedEnumVar); + Assert.assertEquals(testedEnumVar.getOrDefault("name", ""), "ONE"); + Assert.assertEquals(testedEnumVar.getOrDefault("value", ""), "1"); + } + + @Test + public void testGeneratePing() throws Exception { + Map properties = new HashMap<>(); + properties.put(CodegenConstants.API_PACKAGE, "xyz.abcdef.api"); + + File output = Files.createTempDirectory("test").toFile(); + + final CodegenConfigurator configurator = new CodegenConfigurator() + .setGeneratorName("java") + .setLibrary(JavaClientCodegen.OKHTTP_GSON) + .setAdditionalProperties(properties) + .setInputSpec("src/test/resources/3_0/ping.yaml") + .setOutputDir(output.getAbsolutePath().replace("\\", "/")); + + final ClientOptInput clientOptInput = configurator.toClientOptInput(); + DefaultGenerator generator = new DefaultGenerator(); + List files = generator.opts(clientOptInput).generate(); + + Assert.assertEquals(files.size(), 40); + TestUtils.ensureContainsFile(files, output, ".gitignore"); + TestUtils.ensureContainsFile(files, output, ".openapi-generator-ignore"); + TestUtils.ensureContainsFile(files, output, ".openapi-generator/FILES"); + TestUtils.ensureContainsFile(files, output, ".openapi-generator/VERSION"); + TestUtils.ensureContainsFile(files, output, ".travis.yml"); + TestUtils.ensureContainsFile(files, output, "build.gradle"); + TestUtils.ensureContainsFile(files, output, "build.sbt"); + TestUtils.ensureContainsFile(files, output, "docs/DefaultApi.md"); + TestUtils.ensureContainsFile(files, output, "git_push.sh"); + TestUtils.ensureContainsFile(files, output, "gradle.properties"); + TestUtils.ensureContainsFile(files, output, "gradle/wrapper/gradle-wrapper.jar"); + TestUtils.ensureContainsFile(files, output, "gradle/wrapper/gradle-wrapper.properties"); + TestUtils.ensureContainsFile(files, output, "gradlew.bat"); + TestUtils.ensureContainsFile(files, output, "gradlew"); + TestUtils.ensureContainsFile(files, output, "pom.xml"); + TestUtils.ensureContainsFile(files, output, "README.md"); + TestUtils.ensureContainsFile(files, output, "settings.gradle"); + TestUtils.ensureContainsFile(files, output, "api/openapi.yaml"); + TestUtils.ensureContainsFile(files, output, "src/main/AndroidManifest.xml"); + TestUtils.ensureContainsFile(files, output, "src/main/java/xyz/abcdef/api/DefaultApi.java"); + TestUtils.ensureContainsFile(files, output, "src/main/java/xyz/abcdef/ApiCallback.java"); + TestUtils.ensureContainsFile(files, output, "src/main/java/xyz/abcdef/ApiClient.java"); + TestUtils.ensureContainsFile(files, output, "src/main/java/xyz/abcdef/ApiException.java"); + TestUtils.ensureContainsFile(files, output, "src/main/java/xyz/abcdef/ApiResponse.java"); + TestUtils.ensureContainsFile(files, output, "src/main/java/xyz/abcdef/ServerConfiguration.java"); + TestUtils.ensureContainsFile(files, output, "src/main/java/xyz/abcdef/ServerVariable.java"); + TestUtils.ensureContainsFile(files, output, "src/main/java/xyz/abcdef/auth/ApiKeyAuth.java"); + TestUtils.ensureContainsFile(files, output, "src/main/java/xyz/abcdef/auth/Authentication.java"); + TestUtils.ensureContainsFile(files, output, "src/main/java/xyz/abcdef/auth/HttpBasicAuth.java"); + TestUtils.ensureContainsFile(files, output, "src/main/java/xyz/abcdef/auth/HttpBearerAuth.java"); + TestUtils.ensureContainsFile(files, output, "src/main/java/xyz/abcdef/Configuration.java"); + TestUtils.ensureContainsFile(files, output, "src/main/java/xyz/abcdef/GzipRequestInterceptor.java"); + TestUtils.ensureContainsFile(files, output, "src/main/java/xyz/abcdef/JSON.java"); + TestUtils.ensureContainsFile(files, output, "src/main/java/xyz/abcdef/Pair.java"); + TestUtils.ensureContainsFile(files, output, "src/main/java/xyz/abcdef/ProgressRequestBody.java"); + TestUtils.ensureContainsFile(files, output, "src/main/java/xyz/abcdef/ProgressResponseBody.java"); + TestUtils.ensureContainsFile(files, output, "src/main/java/xyz/abcdef/StringUtil.java"); + TestUtils.ensureContainsFile(files, output, "src/test/java/xyz/abcdef/api/DefaultApiTest.java"); + + validateJavaSourceFiles(files); + + TestUtils.assertFileContains(Paths.get(output + "/src/main/java/xyz/abcdef/api/DefaultApi.java"), "public class DefaultApi"); + + output.deleteOnExit(); + } + + @Test + public void testGeneratePingSomeObj() throws Exception { + Map properties = new HashMap<>(); + properties.put(CodegenConstants.MODEL_PACKAGE, "zz.yyyy.model.xxxx"); + properties.put(CodegenConstants.API_PACKAGE, "zz.yyyy.api.xxxx"); + properties.put(CodegenConstants.INVOKER_PACKAGE, "zz.yyyy.invoker.xxxx"); + properties.put(AbstractJavaCodegen.BOOLEAN_GETTER_PREFIX, "is"); + + File output = Files.createTempDirectory("test").toFile(); + + final CodegenConfigurator configurator = new CodegenConfigurator() + .setGeneratorName("java") + .setLibrary(JavaClientCodegen.OKHTTP_GSON) + .setAdditionalProperties(properties) + .setInputSpec("src/test/resources/3_0/pingSomeObj.yaml") + .setOutputDir(output.getAbsolutePath().replace("\\", "/")); + + final ClientOptInput clientOptInput = configurator.toClientOptInput(); + DefaultGenerator generator = new DefaultGenerator(); + List files = generator.opts(clientOptInput).generate(); + + Assert.assertEquals(files.size(), 43); + TestUtils.ensureContainsFile(files, output, ".gitignore"); + TestUtils.ensureContainsFile(files, output, ".openapi-generator-ignore"); + TestUtils.ensureContainsFile(files, output, ".openapi-generator/FILES"); + TestUtils.ensureContainsFile(files, output, ".openapi-generator/VERSION"); + TestUtils.ensureContainsFile(files, output, ".travis.yml"); + TestUtils.ensureContainsFile(files, output, "build.gradle"); + TestUtils.ensureContainsFile(files, output, "build.sbt"); + TestUtils.ensureContainsFile(files, output, "docs/PingApi.md"); + TestUtils.ensureContainsFile(files, output, "docs/SomeObj.md"); + TestUtils.ensureContainsFile(files, output, "git_push.sh"); + TestUtils.ensureContainsFile(files, output, "gradle.properties"); + TestUtils.ensureContainsFile(files, output, "gradle/wrapper/gradle-wrapper.jar"); + TestUtils.ensureContainsFile(files, output, "gradle/wrapper/gradle-wrapper.properties"); + TestUtils.ensureContainsFile(files, output, "gradlew.bat"); + TestUtils.ensureContainsFile(files, output, "gradlew"); + TestUtils.ensureContainsFile(files, output, "pom.xml"); + TestUtils.ensureContainsFile(files, output, "README.md"); + TestUtils.ensureContainsFile(files, output, "settings.gradle"); + TestUtils.ensureContainsFile(files, output, "api/openapi.yaml"); + TestUtils.ensureContainsFile(files, output, "src/main/AndroidManifest.xml"); + TestUtils.ensureContainsFile(files, output, "src/main/java/zz/yyyy/api/xxxx/PingApi.java"); + TestUtils.ensureContainsFile(files, output, "src/main/java/zz/yyyy/invoker/xxxx/ApiCallback.java"); + TestUtils.ensureContainsFile(files, output, "src/main/java/zz/yyyy/invoker/xxxx/ApiClient.java"); + TestUtils.ensureContainsFile(files, output, "src/main/java/zz/yyyy/invoker/xxxx/ApiException.java"); + TestUtils.ensureContainsFile(files, output, "src/main/java/zz/yyyy/invoker/xxxx/ApiResponse.java"); + TestUtils.ensureContainsFile(files, output, "src/main/java/zz/yyyy/invoker/xxxx/ServerConfiguration.java"); + TestUtils.ensureContainsFile(files, output, "src/main/java/zz/yyyy/invoker/xxxx/ServerVariable.java"); + TestUtils.ensureContainsFile(files, output, "src/main/java/zz/yyyy/invoker/xxxx/auth/ApiKeyAuth.java"); + TestUtils.ensureContainsFile(files, output, "src/main/java/zz/yyyy/invoker/xxxx/auth/Authentication.java"); + TestUtils.ensureContainsFile(files, output, "src/main/java/zz/yyyy/invoker/xxxx/auth/HttpBasicAuth.java"); + TestUtils.ensureContainsFile(files, output, "src/main/java/zz/yyyy/invoker/xxxx/auth/HttpBearerAuth.java"); + TestUtils.ensureContainsFile(files, output, "src/main/java/zz/yyyy/invoker/xxxx/Configuration.java"); + TestUtils.ensureContainsFile(files, output, "src/main/java/zz/yyyy/invoker/xxxx/GzipRequestInterceptor.java"); + TestUtils.ensureContainsFile(files, output, "src/main/java/zz/yyyy/invoker/xxxx/JSON.java"); + TestUtils.ensureContainsFile(files, output, "src/main/java/zz/yyyy/invoker/xxxx/Pair.java"); + TestUtils.ensureContainsFile(files, output, "src/main/java/zz/yyyy/invoker/xxxx/ProgressRequestBody.java"); + TestUtils.ensureContainsFile(files, output, "src/main/java/zz/yyyy/invoker/xxxx/ProgressResponseBody.java"); + TestUtils.ensureContainsFile(files, output, "src/main/java/zz/yyyy/invoker/xxxx/StringUtil.java"); + TestUtils.ensureContainsFile(files, output, "src/main/java/zz/yyyy/model/xxxx/SomeObj.java"); + TestUtils.ensureContainsFile(files, output, "src/test/java/zz/yyyy/api/xxxx/PingApiTest.java"); + TestUtils.ensureContainsFile(files, output, "src/test/java/zz/yyyy/model/xxxx/SomeObjTest.java"); + + validateJavaSourceFiles(files); + + TestUtils.assertFileContains(Paths.get(output + "/src/main/java/zz/yyyy/model/xxxx/SomeObj.java"), + "public class SomeObj", + "Boolean isActive()"); + + output.deleteOnExit(); + } + + @Test + public void testJdkHttpClient() throws Exception { + Map properties = new HashMap<>(); + properties.put(CodegenConstants.API_PACKAGE, "xyz.abcdef.api"); + + File output = Files.createTempDirectory("test").toFile(); + output.deleteOnExit(); + + final CodegenConfigurator configurator = new CodegenConfigurator() + .setGeneratorName("java") + .setLibrary(JavaClientCodegen.NATIVE) + .setAdditionalProperties(properties) + .setInputSpec("src/test/resources/3_0/ping.yaml") + .setOutputDir(output.getAbsolutePath().replace("\\", "/")); + + final ClientOptInput clientOptInput = configurator.toClientOptInput(); + DefaultGenerator generator = new DefaultGenerator(); + List files = generator.opts(clientOptInput).generate(); + + Assert.assertEquals(files.size(), 32); + validateJavaSourceFiles(files); + + TestUtils.assertFileContains(Paths.get(output + "/src/main/java/xyz/abcdef/api/DefaultApi.java"), + "public class DefaultApi", + "import java.net.http.HttpClient;", + "import java.net.http.HttpRequest;", + "import java.net.http.HttpResponse;"); + + TestUtils.assertFileContains(Paths.get(output + "/src/main/java/xyz/abcdef/ApiClient.java"), + "public class ApiClient", + "import java.net.http.HttpClient;", + "import java.net.http.HttpRequest;"); + } + + @Test + public void testJdkHttpClientWithAndWithoutDiscriminator() throws Exception { + Map properties = new HashMap<>(); + properties.put(CodegenConstants.API_PACKAGE, "xyz.abcdef.api"); + properties.put(CodegenConstants.MODEL_PACKAGE, "xyz.abcdef.model"); + properties.put(CodegenConstants.INVOKER_PACKAGE, "xyz.abcdef.invoker"); + + File output = Files.createTempDirectory("test").toFile(); + output.deleteOnExit(); + + final CodegenConfigurator configurator = new CodegenConfigurator() + .setGeneratorName("java") + .setLibrary(JavaClientCodegen.NATIVE) + .setAdditionalProperties(properties) + .setInputSpec("src/test/resources/2_0/petstore-with-fake-endpoints-models-for-testing.yaml") + .setOutputDir(output.getAbsolutePath().replace("\\", "/")); + + final ClientOptInput clientOptInput = configurator.toClientOptInput(); + DefaultGenerator generator = new DefaultGenerator(); + generator.setGeneratorPropertyDefault(CodegenConstants.MODELS, "true"); + generator.setGeneratorPropertyDefault(CodegenConstants.APIS, "true"); + List files = generator.opts(clientOptInput).generate(); + + Assert.assertEquals(files.size(), 162); + validateJavaSourceFiles(files); + + TestUtils.assertFileContains(Paths.get(output + "/src/main/java/xyz/abcdef/model/Dog.java"), + "import xyz.abcdef.invoker.JSON;"); + TestUtils.assertFileNotContains(Paths.get(output + "/src/main/java/xyz/abcdef/model/DogAllOf.java"), + "import xyz.abcdef.invoker.JSON;"); + } + + @Test + public void testJdkHttpAsyncClient() throws Exception { + Map properties = new HashMap<>(); + properties.put(CodegenConstants.API_PACKAGE, "xyz.abcdef.api"); + properties.put(JavaClientCodegen.ASYNC_NATIVE, true); + + File output = Files.createTempDirectory("test").toFile(); + output.deleteOnExit(); + + final CodegenConfigurator configurator = new CodegenConfigurator() + .setGeneratorName("java") + .setLibrary(JavaClientCodegen.NATIVE) + .setAdditionalProperties(properties) + .setInputSpec("src/test/resources/3_0/pingSomeObj.yaml") + .setOutputDir(output.getAbsolutePath().replace("\\", "/")); + + final ClientOptInput clientOptInput = configurator.toClientOptInput(); + DefaultGenerator generator = new DefaultGenerator(); + List files = generator.opts(clientOptInput).generate(); + + Assert.assertEquals(files.size(), 35); + validateJavaSourceFiles(files); + + Path defaultApi = Paths.get(output + "/src/main/java/xyz/abcdef/api/PingApi.java"); + TestUtils.assertFileContains(defaultApi, + "public class PingApi", + "import java.net.http.HttpClient;", + "import java.net.http.HttpRequest;", + "import java.net.http.HttpResponse;", + "import java.util.concurrent.CompletableFuture;"); + + Path apiClient = Paths.get(output + "/src/main/java/xyz/abcdef/ApiClient.java"); + TestUtils.assertFileContains(apiClient, + "public class ApiClient", + "import java.net.http.HttpClient;", + "import java.net.http.HttpRequest;"); + } + + @Test + public void testReferencedHeader() { + final OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/3_0/issue855.yaml"); + JavaClientCodegen codegen = new JavaClientCodegen(); + codegen.setOpenAPI(openAPI); + + ApiResponse ok_200 = openAPI.getComponents().getResponses().get("OK_200"); + CodegenResponse response = codegen.fromResponse("200", ok_200); + + Assert.assertEquals(response.headers.size(), 1); + CodegenProperty header = response.headers.get(0); + Assert.assertEquals(header.dataType, "UUID"); + Assert.assertEquals(header.baseName, "Request"); + } + + @Test + public void testAuthorizationScopeValues_Issue392() { + final OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/3_0/issue392.yaml"); + + final DefaultGenerator defaultGenerator = new DefaultGenerator(); + + final ClientOptInput clientOptInput = new ClientOptInput(); + clientOptInput.openAPI(openAPI); + clientOptInput.config(new JavaClientCodegen()); + + defaultGenerator.opts(clientOptInput); + final List codegenOperations = defaultGenerator.processPaths(openAPI.getPaths()).get("Pet"); + + // Verify GET only has 'read' scope + final CodegenOperation getCodegenOperation = codegenOperations.stream().filter(it -> it.httpMethod.equals("GET")).collect(Collectors.toList()).get(0); + assertTrue(getCodegenOperation.hasAuthMethods); + assertEquals(getCodegenOperation.authMethods.size(), 1); + final List> getScopes = getCodegenOperation.authMethods.get(0).scopes; + assertEquals(getScopes.size(), 1, "GET scopes don't match. actual::" + getScopes); + + // POST operation should have both 'read' and 'write' scope on it + final CodegenOperation postCodegenOperation = codegenOperations.stream().filter(it -> it.httpMethod.equals("POST")).collect(Collectors.toList()).get(0); + assertTrue(postCodegenOperation.hasAuthMethods); + assertEquals(postCodegenOperation.authMethods.size(), 1); + final List> postScopes = postCodegenOperation.authMethods.get(0).scopes; + assertEquals(postScopes.size(), 2, "POST scopes don't match. actual:" + postScopes); + } + + @Test + public void testAuthorizationScopeValues_Issue6733() throws IOException { + File output = Files.createTempDirectory("test").toFile(); + output.deleteOnExit(); + + final CodegenConfigurator configurator = new CodegenConfigurator() + .setGeneratorName("java") + .setLibrary(JavaClientCodegen.RESTEASY) + .setValidateSpec(false) + .setInputSpec("src/test/resources/3_0/regression-6734.yaml") + .setOutputDir(output.getAbsolutePath().replace("\\", "/")); + + final ClientOptInput clientOptInput = configurator.toClientOptInput(); + + DefaultGenerator generator = new DefaultGenerator(); + generator.setGeneratorPropertyDefault(CodegenConstants.MODELS, "true"); + generator.setGeneratorPropertyDefault(CodegenConstants.MODEL_TESTS, "false"); + generator.setGeneratorPropertyDefault(CodegenConstants.MODEL_DOCS, "false"); + generator.setGeneratorPropertyDefault(CodegenConstants.APIS, "true"); + // tests if NPE will crash generation when path in yaml arent provided + generator.setGeneratorPropertyDefault(CodegenConstants.SUPPORTING_FILES, "false"); + generator.setGenerateMetadata(false); + List files = generator.opts(clientOptInput).generate(); + + validateJavaSourceFiles(files); + + Assert.assertEquals(files.size(), 1); + files.forEach(File::deleteOnExit); + } + + @Test + public void testAuthorizationsMethodsSizeWhenFiltered() { + final OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/3_0/issue4584.yaml"); + + final DefaultGenerator defaultGenerator = new DefaultGenerator(); + + final ClientOptInput clientOptInput = new ClientOptInput(); + clientOptInput.openAPI(openAPI); + clientOptInput.config(new JavaClientCodegen()); + + defaultGenerator.opts(clientOptInput); + final List codegenOperations = defaultGenerator.processPaths(openAPI.getPaths()).get("Pet"); + + final CodegenOperation getCodegenOperation = codegenOperations.stream().filter(it -> it.httpMethod.equals("GET")).collect(Collectors.toList()).get(0); + assertTrue(getCodegenOperation.hasAuthMethods); + assertEquals(getCodegenOperation.authMethods.size(), 2); + } + + @Test + public void testFreeFormObjects() { + final OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/3_0/issue796.yaml"); + JavaClientCodegen codegen = new JavaClientCodegen(); + + Schema test1 = openAPI.getComponents().getSchemas().get("MapTest1"); + codegen.setOpenAPI(openAPI); + CodegenModel cm1 = codegen.fromModel("MapTest1", test1); + Assert.assertEquals(cm1.getDataType(), "Map"); + Assert.assertEquals(cm1.getParent(), "HashMap"); + Assert.assertEquals(cm1.getClassname(), "MapTest1"); + + Schema test2 = openAPI.getComponents().getSchemas().get("MapTest2"); + codegen.setOpenAPI(openAPI); + CodegenModel cm2 = codegen.fromModel("MapTest2", test2); + Assert.assertEquals(cm2.getDataType(), "Map"); + Assert.assertEquals(cm2.getParent(), "HashMap"); + Assert.assertEquals(cm2.getClassname(), "MapTest2"); + + Schema test3 = openAPI.getComponents().getSchemas().get("MapTest3"); + codegen.setOpenAPI(openAPI); + CodegenModel cm3 = codegen.fromModel("MapTest3", test3); + Assert.assertEquals(cm3.getDataType(), "Map"); + Assert.assertEquals(cm3.getParent(), "HashMap"); + Assert.assertEquals(cm3.getClassname(), "MapTest3"); + + Schema other = openAPI.getComponents().getSchemas().get("OtherObj"); + codegen.setOpenAPI(openAPI); + CodegenModel cm = codegen.fromModel("OtherObj", other); + Assert.assertEquals(cm.getDataType(), "Object"); + Assert.assertEquals(cm.getClassname(), "OtherObj"); + } + + /** + * See https://github.com/OpenAPITools/openapi-generator/issues/3589 + */ + @Test + public void testSchemaMapping() throws IOException { + + Map properties = new HashMap<>(); + properties.put(CodegenConstants.API_PACKAGE, "xyz.abcdef.api"); + + Map schemaMappings = new HashMap<>(); + schemaMappings.put("TypeAlias", "foo.bar.TypeAlias"); + + File output = Files.createTempDirectory("test").toFile(); + output.deleteOnExit(); + + final CodegenConfigurator configurator = new CodegenConfigurator() + .setGeneratorName("java") + .setLibrary(JavaClientCodegen.RESTEASY) + .setAdditionalProperties(properties) + .setSchemaMappings(schemaMappings) + .setGenerateAliasAsModel(true) + .setInputSpec("src/test/resources/3_0/type-alias.yaml") + .setOutputDir(output.getAbsolutePath().replace("\\", "/")); + + final ClientOptInput clientOptInput = configurator.toClientOptInput(); + Assert.assertEquals(clientOptInput.getConfig().schemaMapping().get("TypeAlias"), "foo.bar.TypeAlias"); + + DefaultGenerator generator = new DefaultGenerator(); + generator.setGeneratorPropertyDefault(CodegenConstants.MODELS, "true"); + generator.setGeneratorPropertyDefault(CodegenConstants.MODEL_TESTS, "false"); + generator.setGeneratorPropertyDefault(CodegenConstants.MODEL_DOCS, "false"); + generator.setGeneratorPropertyDefault(CodegenConstants.APIS, "false"); + generator.setGeneratorPropertyDefault(CodegenConstants.SUPPORTING_FILES, "false"); + generator.setGenerateMetadata(false); + List files = generator.opts(clientOptInput).generate(); + files.forEach(File::deleteOnExit); + + validateJavaSourceFiles(files); + + Assert.assertEquals(files.size(), 1); + TestUtils.ensureContainsFile(files, output, "src/main/java/org/openapitools/client/model/ParentType.java"); + + String parentTypeContents = ""; + try { + File file = files.stream().filter(f -> f.getName().endsWith("ParentType.java")).findFirst().get(); + parentTypeContents = new String(Files.readAllBytes(file.toPath()), StandardCharsets.UTF_8); + } catch (IOException ignored) { + + } + + final Pattern FIELD_PATTERN = Pattern.compile(".* private (.*?) typeAlias;.*", Pattern.DOTALL); + Matcher fieldMatcher = FIELD_PATTERN.matcher(parentTypeContents); + Assert.assertTrue(fieldMatcher.matches()); + + // this is the type of the field 'typeAlias'. With a working schemaMapping it should + // be 'foo.bar.TypeAlias' or just 'TypeAlias' + Assert.assertEquals(fieldMatcher.group(1), "foo.bar.TypeAlias"); + } + + @Test + public void testBearerAuth() { + final OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/3_0/pingBearerAuth.yaml"); + JavaClientCodegen codegen = new JavaClientCodegen(); + + List security = codegen.fromSecurity(openAPI.getComponents().getSecuritySchemes()); + Assert.assertEquals(security.size(), 1); + Assert.assertEquals(security.get(0).isBasic, Boolean.TRUE); + Assert.assertEquals(security.get(0).isBasicBasic, Boolean.FALSE); + Assert.assertEquals(security.get(0).isBasicBearer, Boolean.TRUE); + } + + private CodegenProperty codegenPropertyWithArrayOfIntegerValues() { + CodegenProperty array = new CodegenProperty(); + final CodegenProperty items = new CodegenProperty(); + final HashMap allowableValues = new HashMap<>(); + allowableValues.put("values", Collections.singletonList(1)); + items.setAllowableValues(allowableValues); + items.dataType = "Integer"; + array.setItems(items); + array.dataType = "Array"; + array.mostInnerItems = items; + return array; + } + + private CodegenParameter createPathParam(String name) { + CodegenParameter codegenParameter = createStringParam(name); + codegenParameter.isPathParam = true; + return codegenParameter; + } + + private CodegenParameter createQueryParam(String name, boolean required) { + CodegenParameter codegenParameter = createStringParam(name); + codegenParameter.isQueryParam = true; + codegenParameter.required = required; + return codegenParameter; + } + + private CodegenParameter createStringParam(String name) { + CodegenParameter codegenParameter = new CodegenParameter(); + codegenParameter.paramName = name; + codegenParameter.baseName = name; + codegenParameter.dataType = "String"; + return codegenParameter; + } + + @Test + public void escapeName() { + final JavaClientCodegen codegen = new JavaClientCodegen(); + assertEquals(codegen.toApiVarName("Default"), "_default"); + assertEquals(codegen.toApiVarName("int"), "_int"); + assertEquals(codegen.toApiVarName("pony"), "pony"); + } + + @Test + public void testAnyType() { + final OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/3_0/any_type.yaml"); + JavaClientCodegen codegen = new JavaClientCodegen(); + + Schema test1 = openAPI.getComponents().getSchemas().get("AnyValueModel"); + codegen.setOpenAPI(openAPI); + CodegenModel cm1 = codegen.fromModel("AnyValueModel", test1); + Assert.assertEquals(cm1.getClassname(), "AnyValueModel"); + + final CodegenProperty property1 = cm1.allVars.get(0); + Assert.assertEquals(property1.baseName, "any_value"); + Assert.assertEquals(property1.dataType, "Object"); + Assert.assertTrue(property1.isPrimitiveType); + Assert.assertFalse(property1.isContainer); + Assert.assertFalse(property1.isFreeFormObject); + Assert.assertTrue(property1.isAnyType); + + final CodegenProperty property2 = cm1.allVars.get(1); + Assert.assertEquals(property2.baseName, "any_value_with_desc"); + Assert.assertEquals(property2.dataType, "Object"); + Assert.assertFalse(property2.required); + Assert.assertTrue(property2.isPrimitiveType); + Assert.assertFalse(property2.isContainer); + Assert.assertFalse(property2.isFreeFormObject); + Assert.assertTrue(property2.isAnyType); + + final CodegenProperty property3 = cm1.allVars.get(2); + Assert.assertEquals(property3.baseName, "any_value_nullable"); + Assert.assertEquals(property3.dataType, "Object"); + Assert.assertFalse(property3.required); + Assert.assertTrue(property3.isPrimitiveType); + Assert.assertFalse(property3.isContainer); + Assert.assertFalse(property3.isFreeFormObject); + Assert.assertTrue(property3.isAnyType); + + Schema test2 = openAPI.getComponents().getSchemas().get("AnyValueModelInline"); + codegen.setOpenAPI(openAPI); + CodegenModel cm2 = codegen.fromModel("AnyValueModelInline", test2); + Assert.assertEquals(cm2.getClassname(), "AnyValueModelInline"); + + final CodegenProperty cp1 = cm2.vars.get(0); + Assert.assertEquals(cp1.baseName, "any_value"); + Assert.assertEquals(cp1.dataType, "Object"); + Assert.assertFalse(cp1.required); + Assert.assertTrue(cp1.isPrimitiveType); + Assert.assertFalse(cp1.isContainer); + Assert.assertFalse(cp1.isFreeFormObject); + Assert.assertTrue(cp1.isAnyType); + + final CodegenProperty cp2 = cm2.vars.get(1); + Assert.assertEquals(cp2.baseName, "any_value_with_desc"); + Assert.assertEquals(cp2.dataType, "Object"); + Assert.assertFalse(cp2.required); + Assert.assertTrue(cp2.isPrimitiveType); + Assert.assertFalse(cp2.isContainer); + Assert.assertFalse(cp2.isFreeFormObject); + Assert.assertTrue(cp2.isAnyType); + + final CodegenProperty cp3 = cm2.vars.get(2); + Assert.assertEquals(cp3.baseName, "any_value_nullable"); + Assert.assertEquals(cp3.dataType, "Object"); + Assert.assertFalse(cp3.required); + Assert.assertTrue(cp3.isPrimitiveType); + Assert.assertFalse(cp3.isContainer); + Assert.assertFalse(cp3.isFreeFormObject); + Assert.assertTrue(cp3.isAnyType); + + // map + // Should allow in any type including map, https://github.com/swagger-api/swagger-parser/issues/1603 + final CodegenProperty cp4 = cm2.vars.get(3); + Assert.assertEquals(cp4.baseName, "map_any_value"); + Assert.assertEquals(cp4.dataType, "Map"); + Assert.assertFalse(cp4.required); + Assert.assertTrue(cp4.isPrimitiveType); + Assert.assertTrue(cp4.isContainer); + Assert.assertTrue(cp4.isMap); + Assert.assertTrue(cp4.isFreeFormObject); + Assert.assertFalse(cp4.isAnyType); + + // Should allow in any type including map, https://github.com/swagger-api/swagger-parser/issues/1603 + final CodegenProperty cp5 = cm2.vars.get(4); + Assert.assertEquals(cp5.baseName, "map_any_value_with_desc"); + Assert.assertEquals(cp5.dataType, "Map"); + Assert.assertFalse(cp5.required); + Assert.assertTrue(cp5.isPrimitiveType); + Assert.assertTrue(cp5.isContainer); + Assert.assertTrue(cp5.isMap); + Assert.assertTrue(cp5.isFreeFormObject); + Assert.assertFalse(cp5.isAnyType); + + // Should allow in any type including map, https://github.com/swagger-api/swagger-parser/issues/1603 + final CodegenProperty cp6 = cm2.vars.get(5); + Assert.assertEquals(cp6.baseName, "map_any_value_nullable"); + Assert.assertEquals(cp6.dataType, "Map"); + Assert.assertFalse(cp6.required); + Assert.assertTrue(cp6.isPrimitiveType); + Assert.assertTrue(cp6.isContainer); + Assert.assertTrue(cp6.isMap); + Assert.assertTrue(cp6.isFreeFormObject); + Assert.assertFalse(cp6.isAnyType); + + // array + // Should allow in any type including array, https://github.com/swagger-api/swagger-parser/issues/1603 + final CodegenProperty cp7 = cm2.vars.get(6); + Assert.assertEquals(cp7.baseName, "array_any_value"); + Assert.assertEquals(cp7.dataType, "List"); + Assert.assertFalse(cp7.required); + Assert.assertTrue(cp7.isPrimitiveType); + Assert.assertTrue(cp7.isContainer); + Assert.assertTrue(cp7.isArray); + Assert.assertFalse(cp7.isFreeFormObject); + Assert.assertFalse(cp7.isAnyType); + + // Should allow in any type including array, https://github.com/swagger-api/swagger-parser/issues/1603 + final CodegenProperty cp8 = cm2.vars.get(7); + Assert.assertEquals(cp8.baseName, "array_any_value_with_desc"); + Assert.assertEquals(cp8.dataType, "List"); + Assert.assertFalse(cp8.required); + Assert.assertTrue(cp8.isPrimitiveType); + Assert.assertTrue(cp8.isContainer); + Assert.assertTrue(cp8.isArray); + Assert.assertFalse(cp8.isFreeFormObject); + Assert.assertFalse(cp8.isAnyType); + + // Should allow in any type including array, https://github.com/swagger-api/swagger-parser/issues/1603 + final CodegenProperty cp9 = cm2.vars.get(8); + Assert.assertEquals(cp9.baseName, "array_any_value_nullable"); + Assert.assertEquals(cp9.dataType, "List"); + Assert.assertFalse(cp9.required); + Assert.assertTrue(cp9.isPrimitiveType); + Assert.assertTrue(cp9.isContainer); + Assert.assertTrue(cp9.isArray); + Assert.assertFalse(cp9.isFreeFormObject); + Assert.assertFalse(cp9.isAnyType); + } + + /** + * See https://github.com/OpenAPITools/openapi-generator/issues/4803 + * + * UPDATE: the following test has been ignored due to https://github.com/OpenAPITools/openapi-generator/pull/11081/ + * We will contact the contributor of the following test to see if the fix will break their use cases and + * how we can fix it accordingly. + */ + @Test + @Ignore + public void testRestTemplateFormMultipart() throws IOException { + + Map properties = new HashMap<>(); + properties.put(CodegenConstants.API_PACKAGE, "xyz.abcdef.api"); + + + File output = Files.createTempDirectory("test").toFile(); + output.deleteOnExit(); + + final CodegenConfigurator configurator = new CodegenConfigurator() + .setGeneratorName("java") + .setLibrary(JavaClientCodegen.RESTTEMPLATE) + .setAdditionalProperties(properties) + .setInputSpec("src/test/resources/3_0/form-multipart-binary-array.yaml") + .setOutputDir(output.getAbsolutePath().replace("\\", "/")); + + + DefaultGenerator generator = new DefaultGenerator(); + List files = generator.opts(configurator.toClientOptInput()).generate(); + files.forEach(File::deleteOnExit); + + validateJavaSourceFiles(files); + + Path defaultApi = Paths.get(output + "/src/main/java/xyz/abcdef/api/MultipartApi.java"); + TestUtils.assertFileContains(defaultApi, + //multiple files + "multipartArrayWithHttpInfo(List files)", + "formParams.addAll(\"files\", files.stream().map(FileSystemResource::new).collect(Collectors.toList()));", + + //mixed + "multipartMixedWithHttpInfo(File file, MultipartMixedMarker marker)", + "formParams.add(\"file\", new FileSystemResource(file));", + + //single file + "multipartSingleWithHttpInfo(File file)", + "formParams.add(\"file\", new FileSystemResource(file));" + ); + } + + /** + * See https://github.com/OpenAPITools/openapi-generator/issues/4803 + * + * UPDATE: the following test has been ignored due to https://github.com/OpenAPITools/openapi-generator/pull/11081/ + * We will contact the contributor of the following test to see if the fix will break their use cases and + * how we can fix it accordingly. + */ + @Test + @Ignore + public void testWebClientFormMultipart() throws IOException { + + Map properties = new HashMap<>(); + properties.put(CodegenConstants.API_PACKAGE, "xyz.abcdef.api"); + + + File output = Files.createTempDirectory("test").toFile(); + output.deleteOnExit(); + + final CodegenConfigurator configurator = new CodegenConfigurator() + .setGeneratorName("java") + .setLibrary(JavaClientCodegen.WEBCLIENT) + .setAdditionalProperties(properties) + .setInputSpec("src/test/resources/3_0/form-multipart-binary-array.yaml") + .setOutputDir(output.getAbsolutePath().replace("\\", "/")); + + + DefaultGenerator generator = new DefaultGenerator(); + List files = generator.opts(configurator.toClientOptInput()).generate(); + files.forEach(File::deleteOnExit); + + validateJavaSourceFiles(files); + + Path defaultApi = Paths.get(output + "/src/main/java/xyz/abcdef/api/MultipartApi.java"); + TestUtils.assertFileContains(defaultApi, + //multiple files + "multipartArray(List files)", + "formParams.addAll(\"files\", files.stream().map(FileSystemResource::new).collect(Collectors.toList()));", + + //mixed + "multipartMixed(File file, MultipartMixedMarker marker)", + "formParams.add(\"file\", new FileSystemResource(file));", + + //single file + "multipartSingle(File file)", + "formParams.add(\"file\", new FileSystemResource(file));" + ); + } + + @Test + public void shouldGenerateBlockingAndNoBlockingOperationsForWebClient() throws IOException { + Map properties = new HashMap<>(); + properties.put(CodegenConstants.API_PACKAGE, "xyz.abcdef.api"); + properties.put(JavaClientCodegen.WEBCLIENT_BLOCKING_OPERATIONS, true); + + File output = Files.createTempDirectory("test").toFile(); + output.deleteOnExit(); + + final CodegenConfigurator configurator = new CodegenConfigurator() + .setGeneratorName("java") + .setLibrary(JavaClientCodegen.WEBCLIENT) + .setAdditionalProperties(properties) + .setInputSpec("src/test/resources/3_0/petstore-with-fake-endpoints-models-for-testing.yaml") + .setOutputDir(output.getAbsolutePath().replace("\\", "/")); + + DefaultGenerator generator = new DefaultGenerator(); + Map files = generator.opts(configurator.toClientOptInput()).generate().stream() + .collect(Collectors.toMap(File::getName, Function.identity())); + + JavaFileAssert.assertThat(files.get("StoreApi.java")) + .assertMethod("getInventory").hasReturnType("Mono>") //explicit 'x-webclient-blocking: false' which overrides global config + .toFileAssert() + .assertMethod("placeOrder").hasReturnType("Order"); // use global config + + JavaFileAssert.assertThat(files.get("PetApi.java")) + .assertMethod("findPetsByStatus").hasReturnType("List"); // explicit 'x-webclient-blocking: true' which overrides global config + } + + @Test + public void testAllowModelWithNoProperties() throws Exception { + File output = Files.createTempDirectory("test").toFile(); + + final CodegenConfigurator configurator = new CodegenConfigurator() + .setGeneratorName("java") + .setLibrary(JavaClientCodegen.OKHTTP_GSON) + .setInputSpec("src/test/resources/2_0/emptyBaseModel.yaml") + .setOutputDir(output.getAbsolutePath().replace("\\", "/")); + + final ClientOptInput clientOptInput = configurator.toClientOptInput(); + DefaultGenerator generator = new DefaultGenerator(); + List files = generator.opts(clientOptInput).generate(); + + Assert.assertEquals(files.size(), 49); + TestUtils.ensureContainsFile(files, output, "src/main/java/org/openapitools/client/model/RealCommand.java"); + TestUtils.ensureContainsFile(files, output, "src/main/java/org/openapitools/client/model/Command.java"); + + validateJavaSourceFiles(files); + + TestUtils.assertFileContains(Paths.get(output + "/src/main/java/org/openapitools/client/model/RealCommand.java"), + "class RealCommand {"); + + TestUtils.assertFileContains(Paths.get(output + "/src/main/java/org/openapitools/client/model/Command.java"), + "class Command {"); + + output.deleteOnExit(); + } + + /** + * See https://github.com/OpenAPITools/openapi-generator/issues/6715 + * + * UPDATE: the following test has been ignored due to https://github.com/OpenAPITools/openapi-generator/pull/11081/ + * We will contact the contributor of the following test to see if the fix will break their use cases and + * how we can fix it accordingly. + */ + @Test + @Ignore + public void testRestTemplateWithUseAbstractionForFiles() throws IOException { + + Map properties = new HashMap<>(); + properties.put(CodegenConstants.API_PACKAGE, "xyz.abcdef.api"); + properties.put(JavaClientCodegen.USE_ABSTRACTION_FOR_FILES, true); + + + File output = Files.createTempDirectory("test").toFile(); + output.deleteOnExit(); + + final CodegenConfigurator configurator = new CodegenConfigurator() + .setGeneratorName("java") + .setLibrary(JavaClientCodegen.RESTTEMPLATE) + .setAdditionalProperties(properties) + .setInputSpec("src/test/resources/3_0/form-multipart-binary-array.yaml") + .setOutputDir(output.getAbsolutePath().replace("\\", "/")); + + + DefaultGenerator generator = new DefaultGenerator(); + List files = generator.opts(configurator.toClientOptInput()).generate(); + files.forEach(File::deleteOnExit); + + validateJavaSourceFiles(files); + + Path defaultApi = Paths.get(output + "/src/main/java/xyz/abcdef/api/MultipartApi.java"); + TestUtils.assertFileContains(defaultApi, + //multiple files + "multipartArray(java.util.Collection files)", + "multipartArrayWithHttpInfo(java.util.Collection files)", + "formParams.addAll(\"files\", files.stream().collect(Collectors.toList()));", + + //mixed + "multipartMixed(org.springframework.core.io.Resource file, MultipartMixedMarker marker)", + "multipartMixedWithHttpInfo(org.springframework.core.io.Resource file, MultipartMixedMarker marker)", + "formParams.add(\"file\", file);", + + //single file + "multipartSingle(org.springframework.core.io.Resource file)", + "multipartSingleWithHttpInfo(org.springframework.core.io.Resource file)", + "formParams.add(\"file\", file);" + ); + } + + @Test + void testNotDuplicateOauth2FlowsScopes() { + final OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/3_0/issue_7614.yaml"); + + final ClientOptInput clientOptInput = new ClientOptInput() + .openAPI(openAPI) + .config(new JavaClientCodegen()); + + final DefaultGenerator defaultGenerator = new DefaultGenerator(); + defaultGenerator.opts(clientOptInput); + + final Map> paths = defaultGenerator.processPaths(openAPI.getPaths()); + final List codegenOperations = paths.values().stream().flatMap(Collection::stream).collect(Collectors.toList()); + + final CodegenOperation getWithBasicAuthAndOauth = getByOperationId(codegenOperations, "getWithBasicAuthAndOauth"); + assertEquals(getWithBasicAuthAndOauth.authMethods.size(), 3); + assertEquals(getWithBasicAuthAndOauth.authMethods.get(0).name, "basic_auth"); + final Map passwordFlowScope = getWithBasicAuthAndOauth.authMethods.get(1).scopes.get(0); + assertEquals(passwordFlowScope.get("scope"), "something:create"); + assertEquals(passwordFlowScope.get("description"), "create from password flow"); + final Map clientCredentialsFlow = getWithBasicAuthAndOauth.authMethods.get(2).scopes.get(0); + assertEquals(clientCredentialsFlow.get("scope"), "something:create"); + assertEquals(clientCredentialsFlow.get("description"), "create from client credentials flow"); + + final CodegenOperation getWithOauthAuth = getByOperationId(codegenOperations, "getWithOauthAuth"); + assertEquals(getWithOauthAuth.authMethods.size(), 2); + final Map passwordFlow = getWithOauthAuth.authMethods.get(0).scopes.get(0); + assertEquals(passwordFlow.get("scope"), "something:create"); + assertEquals(passwordFlow.get("description"), "create from password flow"); + + final Map clientCredentialsCreateFlow = getWithOauthAuth.authMethods.get(1).scopes.get(0); + assertEquals(clientCredentialsCreateFlow.get("scope"), "something:create"); + assertEquals(clientCredentialsCreateFlow.get("description"), "create from client credentials flow"); + + final Map clientCredentialsProcessFlow = getWithOauthAuth.authMethods.get(1).scopes.get(1); + assertEquals(clientCredentialsProcessFlow.get("scope"), "something:process"); + assertEquals(clientCredentialsProcessFlow.get("description"), "process from client credentials flow"); + } + + private CodegenOperation getByOperationId(List codegenOperations, String operationId) { + return getByCriteria(codegenOperations, (co) -> co.operationId.equals(operationId)) + .orElseThrow(() -> new IllegalStateException(String.format(Locale.ROOT, "Operation with id [%s] does not exist", operationId))); + } + + private Optional getByCriteria(List codegenOperations, Predicate filter){ + return codegenOperations.stream() + .filter(filter) + .findFirst(); + } + + @Test + public void testCustomMethodParamsAreCamelizedWhenUsingFeign() throws IOException { + + File output = Files.createTempDirectory("test").toFile(); + + final CodegenConfigurator configurator = new CodegenConfigurator() + .setGeneratorName("java") + .setLibrary(JavaClientCodegen.FEIGN) + .setInputSpec("src/test/resources/3_0/issue_7791.yaml") + .setOutputDir(output.getAbsolutePath().replace("\\", "/")); + + final ClientOptInput clientOptInput = configurator.toClientOptInput(); + DefaultGenerator generator = new DefaultGenerator(); + List files = generator.opts(clientOptInput).generate(); + + TestUtils.ensureContainsFile(files, output, "src/main/java/org/openapitools/client/api/DefaultApi.java"); + + validateJavaSourceFiles(files); + + TestUtils.assertFileContains(Paths.get(output + "/src/main/java/org/openapitools/client/api/DefaultApi.java"), + "@RequestLine(\"POST /events/{eventId}:undelete\")"); + TestUtils.assertFileNotContains(Paths.get(output + "/src/main/java/org/openapitools/client/api/DefaultApi.java"), + "event_id"); + + // baseName is kept for form parameters + TestUtils.assertFileContains(Paths.get(output + "/src/main/java/org/openapitools/client/api/DefaultApi.java"), + "@Param(\"some_file\") File someFile"); + + output.deleteOnExit(); + } + + /** + * See https://github.com/OpenAPITools/openapi-generator/issues/6715 + * + * UPDATE: the following test has been ignored due to https://github.com/OpenAPITools/openapi-generator/pull/11081/ + * We will contact the contributor of the following test to see if the fix will break their use cases and + * how we can fix it accordingly. + */ + @Test + @Ignore + public void testWebClientWithUseAbstractionForFiles() throws IOException { + + Map properties = new HashMap<>(); + properties.put(CodegenConstants.API_PACKAGE, "xyz.abcdef.api"); + properties.put(JavaClientCodegen.USE_ABSTRACTION_FOR_FILES, true); + + File output = Files.createTempDirectory("test").toFile(); + output.deleteOnExit(); + + final CodegenConfigurator configurator = new CodegenConfigurator() + .setGeneratorName("java") + .setLibrary(JavaClientCodegen.WEBCLIENT) + .setAdditionalProperties(properties) + .setInputSpec("src/test/resources/3_0/form-multipart-binary-array.yaml") + .setOutputDir(output.getAbsolutePath().replace("\\", "/")); + + + DefaultGenerator generator = new DefaultGenerator(); + List files = generator.opts(configurator.toClientOptInput()).generate(); + files.forEach(File::deleteOnExit); + + validateJavaSourceFiles(files); + + Path defaultApi = Paths.get(output + "/src/main/java/xyz/abcdef/api/MultipartApi.java"); + TestUtils.assertFileContains(defaultApi, + //multiple files + "multipartArray(java.util.Collection files)", + "formParams.addAll(\"files\", files.stream().collect(Collectors.toList()));", + + //mixed + "multipartMixed(org.springframework.core.io.AbstractResource file, MultipartMixedMarker marker)", + "formParams.add(\"file\", file);", + + //single file + "multipartSingle(org.springframework.core.io.AbstractResource file)", + "formParams.add(\"file\", file);" + ); + } + + /** + * See https://github.com/OpenAPITools/openapi-generator/issues/8352 + */ + @Test + public void testRestTemplateWithFreeFormInQueryParameters() throws IOException { + final Map properties = new HashMap<>(); + properties.put(CodegenConstants.API_PACKAGE, "xyz.abcdef.api"); + + final File output = Files.createTempDirectory("test") + .toFile(); + output.deleteOnExit(); + + final CodegenConfigurator configurator = new CodegenConfigurator().setGeneratorName("java") + .setLibrary(JavaClientCodegen.RESTTEMPLATE) + .setAdditionalProperties(properties) + .setInputSpec("src/test/resources/3_0/issue8352.yaml") + .setOutputDir(output.getAbsolutePath() + .replace("\\", "/")); + + final DefaultGenerator generator = new DefaultGenerator(); + final List files = generator.opts(configurator.toClientOptInput()) + .generate(); + files.forEach(File::deleteOnExit); + + final Path defaultApi = Paths.get(output + "/src/main/java/xyz/abcdef/ApiClient.java"); + TestUtils.assertFileContains(defaultApi, "value instanceof Map"); + } + + /** + * See https://github.com/OpenAPITools/openapi-generator/issues/8352 + */ + @Test + public void testWebClientWithFreeFormInQueryParameters() throws IOException { + final Map properties = new HashMap<>(); + properties.put(CodegenConstants.API_PACKAGE, "xyz.abcdef.api"); + + final File output = Files.createTempDirectory("test") + .toFile(); + output.deleteOnExit(); + + final CodegenConfigurator configurator = new CodegenConfigurator().setGeneratorName("java") + .setLibrary(JavaClientCodegen.WEBCLIENT) + .setAdditionalProperties(properties) + .setInputSpec("src/test/resources/3_0/issue8352.yaml") + .setOutputDir(output.getAbsolutePath() + .replace("\\", "/")); + + final DefaultGenerator generator = new DefaultGenerator(); + final List files = generator.opts(configurator.toClientOptInput()) + .generate(); + files.forEach(File::deleteOnExit); + + validateJavaSourceFiles(files); + + final Path defaultApi = Paths.get(output + "/src/main/java/xyz/abcdef/ApiClient.java"); + TestUtils.assertFileContains(defaultApi, "value instanceof Map"); + } + + /** + * See https://github.com/OpenAPITools/openapi-generator/issues/11242 + */ + @Test + public void testNativeClientWhiteSpacePathParamEncoding() throws IOException { + Map properties = new HashMap<>(); + properties.put(CodegenConstants.API_PACKAGE, "xyz.abcdef.api"); + + File output = Files.createTempDirectory("test").toFile(); + output.deleteOnExit(); + + final CodegenConfigurator configurator = new CodegenConfigurator() + .setGeneratorName("java") + .setLibrary(JavaClientCodegen.NATIVE) + .setAdditionalProperties(properties) + .setInputSpec("src/test/resources/3_0/issue11242.yaml") + .setOutputDir(output.getAbsolutePath().replace("\\", "/")); + + final ClientOptInput clientOptInput = configurator.toClientOptInput(); + DefaultGenerator generator = new DefaultGenerator(); + List files = generator.opts(clientOptInput).generate(); + + Assert.assertEquals(files.size(), 35); + validateJavaSourceFiles(files); + + TestUtils.assertFileContains(Paths.get(output + "/src/main/java/xyz/abcdef/ApiClient.java"), + "public static String urlEncode(String s) { return URLEncoder.encode(s, UTF_8).replaceAll(\"\\\\+\", \"%20\"); }"); + } + + /** + * See https://github.com/OpenAPITools/openapi-generator/issues/4808 + */ + @Test + public void testNativeClientExplodedQueryParamObject() throws IOException { + Map properties = new HashMap<>(); + properties.put(CodegenConstants.API_PACKAGE, "xyz.abcdef.api"); + + File output = Files.createTempDirectory("test").toFile(); + output.deleteOnExit(); + + final CodegenConfigurator configurator = new CodegenConfigurator() + .setGeneratorName("java") + .setLibrary(JavaClientCodegen.NATIVE) + .setAdditionalProperties(properties) + .setInputSpec("src/test/resources/3_0/issue4808.yaml") + .setOutputDir(output.getAbsolutePath().replace("\\", "/")); + + final ClientOptInput clientOptInput = configurator.toClientOptInput(); + DefaultGenerator generator = new DefaultGenerator(); + List files = generator.opts(clientOptInput).generate(); + + Assert.assertEquals(files.size(), 38); + validateJavaSourceFiles(files); + + TestUtils.assertFileContains(Paths.get(output + "/src/main/java/xyz/abcdef/api/DefaultApi.java"), + "localVarQueryParams.addAll(ApiClient.parameterToPairs(\"since\", queryObject.getSince()));", + "localVarQueryParams.addAll(ApiClient.parameterToPairs(\"sinceBuild\", queryObject.getSinceBuild()));", + "localVarQueryParams.addAll(ApiClient.parameterToPairs(\"maxBuilds\", queryObject.getMaxBuilds()));", + "localVarQueryParams.addAll(ApiClient.parameterToPairs(\"maxWaitSecs\", queryObject.getMaxWaitSecs()));" + ); + } + + @Test + public void testExtraAnnotationsNative() throws IOException { + testExtraAnnotations(JavaClientCodegen.NATIVE); + } + + @Test + public void testExtraAnnotationsJersey1() throws IOException { + testExtraAnnotations(JavaClientCodegen.JERSEY1); + } + + @Test + public void testExtraAnnotationsJersey2() throws IOException { + testExtraAnnotations(JavaClientCodegen.JERSEY2); + } + + @Test + public void testExtraAnnotationsMicroprofile() throws IOException { + testExtraAnnotations(JavaClientCodegen.MICROPROFILE); + } + + @Test + public void testExtraAnnotationsOKHttpGSON() throws IOException { + testExtraAnnotations(JavaClientCodegen.OKHTTP_GSON); + } + + @Test + public void testExtraAnnotationsVertx() throws IOException { + testExtraAnnotations(JavaClientCodegen.VERTX); + } + + @Test + public void testExtraAnnotationsFeign() throws IOException { + testExtraAnnotations(JavaClientCodegen.FEIGN); + } + + @Test + public void testExtraAnnotationsRetrofit2() throws IOException { + testExtraAnnotations(JavaClientCodegen.RETROFIT_2); + } + + @Test + public void testExtraAnnotationsRestTemplate() throws IOException { + testExtraAnnotations(JavaClientCodegen.RESTTEMPLATE); + } + + @Test + public void testExtraAnnotationsWebClient() throws IOException { + testExtraAnnotations(JavaClientCodegen.WEBCLIENT); + } + + @Test + public void testExtraAnnotationsRestEasy() throws IOException { + testExtraAnnotations(JavaClientCodegen.RESTEASY); + } + + @Test + public void testExtraAnnotationsGoogleApiClient() throws IOException { + testExtraAnnotations(JavaClientCodegen.GOOGLE_API_CLIENT); + } + + @Test + public void testExtraAnnotationsRestAssured() throws IOException { + testExtraAnnotations(JavaClientCodegen.REST_ASSURED); + } + + @Test + public void testExtraAnnotationsApache() throws IOException { + testExtraAnnotations(JavaClientCodegen.APACHE); + } + + @Test + public void testDefaultMicroprofileRestClientVersion() throws Exception { + File output = Files.createTempDirectory("test").toFile(); + + final CodegenConfigurator configurator = new CodegenConfigurator() + .setGeneratorName("java") + .setLibrary(JavaClientCodegen.MICROPROFILE) + .setInputSpec("src/test/resources/3_0/petstore.yaml") + .setOutputDir(output.getAbsolutePath().replace("\\", "/")); + + final ClientOptInput clientOptInput = configurator.toClientOptInput(); + DefaultGenerator generator = new DefaultGenerator(); + List files = generator.opts(clientOptInput).generate(); + + TestUtils.ensureContainsFile(files, output, "pom.xml"); + + validateJavaSourceFiles(files); + + TestUtils.assertFileContains(Paths.get(output + "/pom.xml"), + "2.0"); + TestUtils.assertFileContains(Paths.get(output + "/pom.xml"), + "1.2.1"); + TestUtils.assertFileContains(Paths.get(output + "/pom.xml"), + "1.8"); + TestUtils.assertFileContains(Paths.get(output + "/src/main/java/org/openapitools/client/api/PetApi.java"), + "import javax."); + + output.deleteOnExit(); + } + + @Test + public void testMicroprofileRestClientVersion_1_4_1() throws Exception { + Map properties = new HashMap<>(); + properties.put(JavaClientCodegen.MICROPROFILE_REST_CLIENT_VERSION, "1.4.1"); + + File output = Files.createTempDirectory("test").toFile(); + + final CodegenConfigurator configurator = new CodegenConfigurator() + .setAdditionalProperties(properties) + .setGeneratorName("java") + .setLibrary(JavaClientCodegen.MICROPROFILE) + .setInputSpec("src/test/resources/3_0/petstore.yaml") + .setOutputDir(output.getAbsolutePath().replace("\\", "/")); + + final ClientOptInput clientOptInput = configurator.toClientOptInput(); + DefaultGenerator generator = new DefaultGenerator(); + List files = generator.opts(clientOptInput).generate(); + + TestUtils.ensureContainsFile(files, output, "pom.xml"); + + validateJavaSourceFiles(files); + + TestUtils.assertFileContains(Paths.get(output + "/pom.xml"), + "1.4.1"); + TestUtils.assertFileContains(Paths.get(output + "/pom.xml"), + "1.2.1"); + TestUtils.assertFileContains(Paths.get(output + "/pom.xml"), + "1.8"); + TestUtils.assertFileContains(Paths.get(output + "/src/main/java/org/openapitools/client/api/PetApi.java"), + "import javax."); + + output.deleteOnExit(); + } + + @Test(expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = "Version incorrectVersion of MicroProfile Rest Client is not supported or incorrect. Supported versions are 1.4.1, 2.0, 3.0") + public void testMicroprofileRestClientIncorrectVersion() throws Exception { + Map properties = new HashMap<>(); + properties.put(JavaClientCodegen.MICROPROFILE_REST_CLIENT_VERSION, "incorrectVersion"); + + File output = Files.createTempDirectory("test").toFile(); + output.deleteOnExit(); + + final CodegenConfigurator configurator = new CodegenConfigurator() + .setAdditionalProperties(properties) + .setGeneratorName("java") + .setLibrary(JavaClientCodegen.MICROPROFILE) + .setInputSpec("src/test/resources/3_0/petstore.yaml") + .setOutputDir(output.getAbsolutePath().replace("\\", "/")); + + final ClientOptInput clientOptInput = configurator.toClientOptInput(); + DefaultGenerator generator = new DefaultGenerator(); + generator.opts(clientOptInput).generate(); + fail("Expected an exception that did not occur"); + } + + @Test + public void testMicroprofileRestClientVersion_3_0() throws Exception { + Map properties = new HashMap<>(); + properties.put(JavaClientCodegen.MICROPROFILE_REST_CLIENT_VERSION, "3.0"); + + File output = Files.createTempDirectory("test").toFile(); + + final CodegenConfigurator configurator = new CodegenConfigurator() + .setAdditionalProperties(properties) + .setGeneratorName("java") + .setLibrary(JavaClientCodegen.MICROPROFILE) + .setInputSpec("src/test/resources/3_0/petstore.yaml") + .setOutputDir(output.getAbsolutePath().replace("\\", "/")); + + final ClientOptInput clientOptInput = configurator.toClientOptInput(); + DefaultGenerator generator = new DefaultGenerator(); + List files = generator.opts(clientOptInput).generate(); + + TestUtils.ensureContainsFile(files, output, "pom.xml"); + + validateJavaSourceFiles(files); + + TestUtils.assertFileContains(Paths.get(output + "/pom.xml"), + "3.0"); + TestUtils.assertFileContains(Paths.get(output + "/pom.xml"), + "3.0.4"); + TestUtils.assertFileContains(Paths.get(output + "/pom.xml"), + "11"); + TestUtils.assertFileContains(Paths.get(output + "/src/main/java/org/openapitools/client/api/PetApi.java"), + "import jakarta."); + + output.deleteOnExit(); + } + + @Test + public void testMicroprofileGenerateCorrectJsonbCreator_issue12622() throws Exception { + Map properties = new HashMap<>(); + properties.put(JavaClientCodegen.MICROPROFILE_REST_CLIENT_VERSION, "3.0"); + + File output = Files.createTempDirectory("test").toFile(); + output.deleteOnExit(); + + final CodegenConfigurator configurator = new CodegenConfigurator() + .setAdditionalProperties(properties) + .setGeneratorName("java") + .setLibrary(JavaClientCodegen.MICROPROFILE) + .setInputSpec("src/test/resources/bugs/issue_12622.json") + .setOutputDir(output.getAbsolutePath().replace("\\", "/")); + + final ClientOptInput clientOptInput = configurator.toClientOptInput(); + DefaultGenerator generator = new DefaultGenerator(); + Map files = generator.opts(clientOptInput).generate().stream() + .collect(Collectors.toMap(File::getName, Function.identity())); + + JavaFileAssert.assertThat(files.get("Foo.java")) + .assertConstructor("String", "Integer") + .hasParameter("b") + .assertParameterAnnotations() + .containsWithNameAndAttributes("JsonbProperty", ImmutableMap.of("value", "\"b\"", "nillable", "true")) + .toParameter().toConstructor() + .hasParameter("c") + .assertParameterAnnotations() + .containsWithNameAndAttributes("JsonbProperty", ImmutableMap.of("value", "\"c\"")); + } + + @Test + public void testWebClientJsonCreatorWithNullable_issue12790() throws Exception { + Map properties = new HashMap<>(); + properties.put(AbstractJavaCodegen.OPENAPI_NULLABLE, "true"); + + File output = Files.createTempDirectory("test").toFile(); + output.deleteOnExit(); + + final CodegenConfigurator configurator = new CodegenConfigurator() + .setAdditionalProperties(properties) + .setGeneratorName("java") + .setLibrary(JavaClientCodegen.WEBCLIENT) + .setInputSpec("src/test/resources/bugs/issue_12790.yaml") + .setOutputDir(output.getAbsolutePath().replace("\\", "/")); + + final ClientOptInput clientOptInput = configurator.toClientOptInput(); + DefaultGenerator generator = new DefaultGenerator(); + Map files = generator.opts(clientOptInput).generate().stream() + .collect(Collectors.toMap(File::getName, Function.identity())); + + JavaFileAssert.assertThat(files.get("TestObject.java")) + .printFileContent() + .assertConstructor("String", "String") + .bodyContainsLines( + "this.nullableProperty = nullableProperty == null ? JsonNullable.undefined() : JsonNullable.of(nullableProperty);", + "this.notNullableProperty = notNullableProperty;" + ); + } + + @Test + public void testRestTemplateResponseTypeWithUseAbstractionForFiles() throws IOException { + + Map properties = new HashMap<>(); + properties.put(CodegenConstants.API_PACKAGE, "xyz.abcdef.api"); + properties.put(JavaClientCodegen.USE_ABSTRACTION_FOR_FILES, true); + + File output = Files.createTempDirectory("test").toFile(); + output.deleteOnExit(); + + final CodegenConfigurator configurator = new CodegenConfigurator() + .setGeneratorName("java") + .setLibrary(JavaClientCodegen.RESTTEMPLATE) + .setAdditionalProperties(properties) + .setInputSpec("src/test/resources/3_0/issue13146_file_abstraction_response.yaml") + .setOutputDir(output.getAbsolutePath().replace("\\", "/")); + + + DefaultGenerator generator = new DefaultGenerator(); + List files = generator.opts(configurator.toClientOptInput()).generate(); + files.forEach(File::deleteOnExit); + + validateJavaSourceFiles(files); + + Path defaultApi = Paths.get(output + "/src/main/java/xyz/abcdef/api/ResourceApi.java"); + TestUtils.assertFileContains(defaultApi, + "org.springframework.core.io.Resource resourceInResponse()", + "ResponseEntity resourceInResponseWithHttpInfo()", + "ParameterizedTypeReference localReturnType = new ParameterizedTypeReference()" + ); + } + + public void testExtraAnnotations(String library) throws IOException { + File output = Files.createTempDirectory("test").toFile().getCanonicalFile(); + output.deleteOnExit(); + String outputPath = output.getAbsolutePath().replace('\\', '/'); + + Map properties = new HashMap<>(); + properties.put(CXFServerFeatures.LOAD_TEST_DATA_FROM_FILE, "true"); + + final CodegenConfigurator configurator = new CodegenConfigurator() + .setGeneratorName("java") + .setLibrary(library) + .setAdditionalProperties(properties) + .setInputSpec("src/test/resources/3_0/issue_11772.yml") + .setOutputDir(output.getAbsolutePath().replace("\\", "/")); + + final ClientOptInput clientOptInput = configurator.toClientOptInput(); + DefaultGenerator generator = new DefaultGenerator(); + + generator.setGeneratorPropertyDefault(CodegenConstants.MODELS, "true"); + generator.setGeneratorPropertyDefault(CodegenConstants.MODEL_TESTS, "false"); + generator.setGeneratorPropertyDefault(CodegenConstants.MODEL_DOCS, "false"); + generator.setGeneratorPropertyDefault(CodegenConstants.APIS, "false"); + generator.setGeneratorPropertyDefault(CodegenConstants.SUPPORTING_FILES, "false"); + generator.opts(clientOptInput).generate(); + + TestUtils.assertExtraAnnotationFiles(outputPath + "/src/main/java/org/openapitools/client/model"); + + } + + /** + * See https://github.com/OpenAPITools/openapi-generator/issues/11340 + */ + @Test + public void testReferencedHeader2() throws Exception { + File output = Files.createTempDirectory("test").toFile().getCanonicalFile(); + output.deleteOnExit(); + Map additionalProperties = new HashMap<>(); + additionalProperties.put(BeanValidationFeatures.USE_BEANVALIDATION, "true"); + final CodegenConfigurator configurator = new CodegenConfigurator().setGeneratorName("java") + .setAdditionalProperties(additionalProperties) + .setInputSpec("src/test/resources/3_0/issue-11340.yaml") + .setOutputDir(output.getAbsolutePath() + .replace("\\", "/")); + + final ClientOptInput clientOptInput = configurator.toClientOptInput(); + DefaultGenerator generator = new DefaultGenerator(); + + Map files = generator.opts(clientOptInput).generate().stream() + .collect(Collectors.toMap(File::getName, Function.identity())); + + JavaFileAssert.assertThat(files.get("DefaultApi.java")) + .assertMethod("operationWithHttpInfo") + .hasParameter("requestBody") + .assertParameterAnnotations() + .containsWithName("NotNull") + .toParameter().toMethod() + .hasParameter("xNonNullHeaderParameter") + .assertParameterAnnotations() + .containsWithName("NotNull"); + } + + @Test + public void testNativeClientExplodedQueryParamWithArrayProperty() throws IOException { + Map properties = new HashMap<>(); + properties.put(CodegenConstants.API_PACKAGE, "xyz.abcdef.api"); + + File output = Files.createTempDirectory("test").toFile(); + output.deleteOnExit(); + + final CodegenConfigurator configurator = new CodegenConfigurator() + .setGeneratorName("java") + .setLibrary(JavaClientCodegen.NATIVE) + .setAdditionalProperties(properties) + .setInputSpec("src/test/resources/3_0/exploded-query-param-array.yaml") + .setOutputDir(output.getAbsolutePath().replace("\\", "/")); + + final ClientOptInput clientOptInput = configurator.toClientOptInput(); + DefaultGenerator generator = new DefaultGenerator(); + generator.opts(clientOptInput).generate(); + + TestUtils.assertFileContains(Paths.get(output + "/src/main/java/xyz/abcdef/api/DefaultApi.java"), + "localVarQueryParams.addAll(ApiClient.parameterToPairs(\"multi\", \"values\", queryObject.getValues()));" + ); + } + + @Test + public void testJdkHttpClientWithAndWithoutParentExtension() throws Exception { + Map properties = new HashMap<>(); + properties.put(CodegenConstants.API_PACKAGE, "xyz.abcdef.api"); + properties.put(CodegenConstants.MODEL_PACKAGE, "xyz.abcdef.model"); + properties.put(CodegenConstants.INVOKER_PACKAGE, "xyz.abcdef.invoker"); + + File output = Files.createTempDirectory("test").toFile(); + output.deleteOnExit(); + + final CodegenConfigurator configurator = new CodegenConfigurator() + .setGeneratorName("java") + // use default `okhttp-gson` + //.setLibrary(JavaClientCodegen.NATIVE) + .setAdditionalProperties(properties) + .setInputSpec("src/test/resources/3_0/allOf_extension_parent.yaml") + .setOutputDir(output.getAbsolutePath().replace("\\", "/")); + + final ClientOptInput clientOptInput = configurator.toClientOptInput(); + DefaultGenerator generator = new DefaultGenerator(); + generator.setGeneratorPropertyDefault(CodegenConstants.MODELS, "true"); + generator.setGeneratorPropertyDefault(CodegenConstants.APIS, "true"); + List files = generator.opts(clientOptInput).generate(); + + Assert.assertEquals(files.size(), 33); + validateJavaSourceFiles(files); + + TestUtils.assertFileContains(Paths.get(output + "/src/main/java/xyz/abcdef/model/Child.java"), + "public class Child extends Person {"); + TestUtils.assertFileContains(Paths.get(output + "/src/main/java/xyz/abcdef/model/Adult.java"), + "public class Adult extends Person {"); + TestUtils.assertFileContains(Paths.get(output + "/src/main/java/xyz/abcdef/model/AnotherChild.java"), + "public class AnotherChild {"); + } +} diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/options/N4jsClientCodegenOptionsProvider.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/options/N4jsClientCodegenOptionsProvider.java new file mode 100644 index 00000000000..917c951f0b4 --- /dev/null +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/options/N4jsClientCodegenOptionsProvider.java @@ -0,0 +1,39 @@ +package org.openapitools.codegen.options; + +import java.util.Map; + +import org.openapitools.codegen.CodegenConstants; +import org.openapitools.codegen.languages.N4jsClientCodegen; + +import com.google.common.collect.ImmutableMap; + +public class N4jsClientCodegenOptionsProvider implements OptionsProvider { + public static final String PROJECT_NAME_VALUE = "OpenAPI"; + public static final String CHECK_REQUIRED_PARAMS_NOT_NULL__VALUE = "true"; + public static final String CHECK_SUPERFLUOUS_BODY_PROPS__VALUE = "true"; + public static final String GENERATE_DEFAULT_API_EXECUTER__VALUE = "true"; + + @Override + public String getLanguage() { + return "n4js"; + } + + @Override + public Map createOptions() { + ImmutableMap.Builder builder = new ImmutableMap.Builder(); + return builder + .put(N4jsClientCodegen.CHECK_REQUIRED_PARAMS_NOT_NULL, CHECK_REQUIRED_PARAMS_NOT_NULL__VALUE) + .put(N4jsClientCodegen.CHECK_SUPERFLUOUS_BODY_PROPS, CHECK_SUPERFLUOUS_BODY_PROPS__VALUE) + .put(N4jsClientCodegen.GENERATE_DEFAULT_API_EXECUTER, GENERATE_DEFAULT_API_EXECUTER__VALUE) + .put(CodegenConstants.API_PACKAGE, "") + .put(CodegenConstants.MODEL_PACKAGE, "") + .put(CodegenConstants.API_NAME_PREFIX, "") + .build(); + } + + @Override + public boolean isServer() { + return false; + } +} + diff --git a/samples/client/petstore/n4js/.openapi-generator-ignore b/samples/client/petstore/n4js/.openapi-generator-ignore new file mode 100644 index 00000000000..7484ee590a3 --- /dev/null +++ b/samples/client/petstore/n4js/.openapi-generator-ignore @@ -0,0 +1,23 @@ +# OpenAPI Generator Ignore +# Generated by openapi-generator https://github.com/openapitools/openapi-generator + +# Use this file to prevent files from being overwritten by the generator. +# The patterns follow closely to .gitignore or .dockerignore. + +# As an example, the C# client generator defines ApiClient.cs. +# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line: +#ApiClient.cs + +# You can match any string of characters against a directory, file or extension with a single asterisk (*): +#foo/*/qux +# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux + +# You can recursively match patterns against a directory, file or extension with a double asterisk (**): +#foo/**/qux +# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux + +# You can also negate patterns with an exclamation (!). +# For example, you can ignore all files in a docs folder with the file extension .md: +#docs/*.md +# Then explicitly reverse the ignore rule for a single file: +#!docs/README.md diff --git a/samples/client/petstore/n4js/.openapi-generator/FILES b/samples/client/petstore/n4js/.openapi-generator/FILES new file mode 100644 index 00000000000..05a89b603e6 --- /dev/null +++ b/samples/client/petstore/n4js/.openapi-generator/FILES @@ -0,0 +1,12 @@ +.openapi-generator-ignore +README.md +api/ApiHelper.n4js +api/PetApi.n4js +api/StoreApi.n4js +api/UserApi.n4js +model/ApiResponse.n4jsd +model/Category.n4jsd +model/Order.n4jsd +model/Pet.n4jsd +model/Tag.n4jsd +model/User.n4jsd diff --git a/samples/client/petstore/n4js/.openapi-generator/VERSION b/samples/client/petstore/n4js/.openapi-generator/VERSION new file mode 100644 index 00000000000..7f4d792ec2c --- /dev/null +++ b/samples/client/petstore/n4js/.openapi-generator/VERSION @@ -0,0 +1 @@ +6.5.0-SNAPSHOT \ No newline at end of file diff --git a/samples/client/petstore/n4js/README.md b/samples/client/petstore/n4js/README.md new file mode 100644 index 00000000000..f7d1f439a6f --- /dev/null +++ b/samples/client/petstore/n4js/README.md @@ -0,0 +1,48 @@ +# Documentation for OpenAPI Petstore + +- API version: 1.0.0 + +This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters. + + +*Automatically generated by the [OpenAPI Generator](https://openapi-generator.tech)* + + +## Getting started + +Configure the following elements: +- In open-api-n4js.yaml please add under 'additionalProperties': + - property 'fetchExecuterConstName' + - property 'fetchExecuterConstImplPath' +- The generated output directory needs to be augmented with an implementing n4js file + +## Example + +**open-api-n4js.yaml** +```yaml + generatorName: n4js + outputDir: /working_dir/gen-n4js/ + inputSpec: /working_dir/api-spec/main.yaml + templateDir: /openapi-generator/modules/openapi-generator/src/main/resources/n4js + additionalProperties: + fetchExecuterConstName: "FETCH_EXEC" + fetchExecuterConstImplPath: "FetchExecuterImpl" +``` + +**FetchExecuterImpl.n4js** +```typescript + import {FetchExecuterI} from "api/ApiHelper"; + + export public const FETCH_EXEC = new FetchExecuterMock(); + + export public class FetchExecuterMock implements FetchExecuterI { + @Override + public async run( + path: string, + query: ~Object=, + reqInit: ~Object= {}): ~Object with {get status() : number, json(): Promise} { + + return null; + } + } +``` diff --git a/samples/client/petstore/n4js/api/ApiHelper.n4js b/samples/client/petstore/n4js/api/ApiHelper.n4js new file mode 100644 index 00000000000..340ed3ba251 --- /dev/null +++ b/samples/client/petstore/n4js/api/ApiHelper.n4js @@ -0,0 +1,113 @@ + +/** + * Implemented by client + */ +export public interface ~ApiExecuterI { + public async exec( + method: string, + path: string, + pathParams: ~Object+, + queryParams: ~Object+, + headerParams: ~Object+, + payloadContentType: string, + body: any+) : Promise>; +} + +export public interface ~ApiError { + public resultBody?: T; +} + +export public function checkRequiredParams(apiName: string, params: ~Object+) : void { + for (const key of Object.keys(params)) { + const arg = params[key]; + if (arg == null) { + throw new Error('Required parameter ' + key + ' was null or undefined when calling ' + apiName + '.'); + } + } +} + +export public function cleanCopyBody(t : T+, ...properties: string) : ~T { + const copy : ~T+ = {}; + for (const prop in properties) { + copy[prop] = t.prop; + } + return copy; +} + +/** + * Default implementation of ApiExecuterI + * + * The following dependencies are necessary: + * - n4js-runtime-esnext + * - n4js-runtime-es2015 + * - n4js-runtime-html5 + */ +export public class FetchApiExec implements ApiExecuterI { + public apiOrigin: string; + const jsonTypes = ["application/json", "application/problem+json"]; + + @Override + public async exec( + method: string, + path: string, + pathParams: ~Object+, + queryParams: ~Object+, + headerParams: ~Object+, + payloadContentType: string, + body: any+ + ): Promise> { + + if (pathParams) { + for (const [k, v] of Object.entries(pathParams)) { + path = path.replace(`{${k}}`, encodeURIComponent(String(v))); + } + } + const query: string[] = []; + if (queryParams) { + for (const [k, v] of Object.entries(queryParams)) { + query.push(`${k}=${encodeURIComponent(String(v))}`); + } + } + + let url = `${this.apiOrigin}${path}`; + if (query.length) { + url += `?${query.join("&")}`; + } + + const headers: Object+ = {}; + if (payloadContentType) { + headers["content-type"] = payloadContentType; + if (this.constructor.jsonTypes.includes(payloadContentType)) { + body = JSON.stringify(body); + } + } + Object.assign(headers, headerParams); + + return await this.fetchExec(url, { + method, + headers, + body, + }); + } + + protected async fetchExec(url: string, reqInit: RequestInit): Promise> { + const resp = await fetch(url, reqInit); + + if (resp.status !== 204) { + const contentType = (resp.headers.get("content-type") || "").split(";")[0]; + const body = this.constructor.jsonTypes.includes(contentType) + ? await resp.json() + : await resp.text(); + + if (!resp.ok) { + await this.handleError(resp, body); + } + return body as R; + } + return null; + } + + protected async handleError(resp: Response, body): Promise> { + throw {body: body}; + } +} diff --git a/samples/client/petstore/n4js/api/PetApi.n4js b/samples/client/petstore/n4js/api/PetApi.n4js new file mode 100644 index 00000000000..9ffcf762ab3 --- /dev/null +++ b/samples/client/petstore/n4js/api/PetApi.n4js @@ -0,0 +1,223 @@ +/* + * OpenAPI Petstore + * This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters. + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +import { ApiExecuterI, ApiError, checkRequiredParams, cleanCopyBody } from "api/ApiHelper" +import { ApiResponse } from 'model/ApiResponse'; +import { Pet } from 'model/Pet'; + + + + +/** + * + * @summary Add a new pet to the store + * @param fe Callback interface that runs the fetch query + * @param pet Pet object that needs to be added to the store + * @response 200 [Pet] successful operation + * @response 405 [undefined] Invalid input + */ +export public async function PetApi__addPet(fe : ApiExecuterI, pet: Pet) : Promise> { + checkRequiredParams('addPet', { 'pet': pet }); + + const _pathParams = { }; + const _queryParams = { }; + const _headerParams = { }; + const _body = cleanCopyBody(pet, 'id', 'category', 'name', 'photoUrls', 'tags', 'status'); + + return await fe.exec( + 'POST', '/v2' + '/pet', + _pathParams, _queryParams, _headerParams, + undefined, + _body + ); +} + +/** + * + * @summary Deletes a pet + * @param fe Callback interface that runs the fetch query + * @param petId Pet id to delete + * @param apiKey + * @response 400 [undefined] Invalid pet value + */ +export public async function PetApi__deletePet(fe : ApiExecuterI, petId: int, apiKey: string=) : Promise> { + checkRequiredParams('deletePet', { 'petId': petId, }); + + const _pathParams = { + 'petId': petId }; + const _queryParams = { }; + const _headerParams = { + 'api_key': apiKey }; + const _body = undefined; + + await fe.exec( + 'DELETE', '/v2' + '/pet/{petId}', + _pathParams, _queryParams, _headerParams, + undefined, + _body + ); +} + +/** + * Multiple status values can be provided with comma separated strings + * @summary Finds Pets by status + * @param fe Callback interface that runs the fetch query + * @param status Status values that need to be considered for filter + * @response 200 [Pet[]] successful operation + * @response 400 [undefined] Invalid status value + */ +export public async function PetApi__findPetsByStatus(fe : ApiExecuterI, status: "available" | "pending" | "sold"[]) : Promise> { + checkRequiredParams('findPetsByStatus', { 'status': status }); + + const _pathParams = { }; + const _queryParams = { + 'status': status }; + const _headerParams = { }; + const _body = undefined; + + return await fe.exec( + 'GET', '/v2' + '/pet/findByStatus', + _pathParams, _queryParams, _headerParams, + undefined, + _body + ); +} + +/** + * Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing. + * @summary Finds Pets by tags + * @param fe Callback interface that runs the fetch query + * @param tags Tags to filter by + * @response 200 [Pet[]] successful operation + * @response 400 [undefined] Invalid tag value + */ +export public async function PetApi__findPetsByTags(fe : ApiExecuterI, tags: string[]) : Promise> { + checkRequiredParams('findPetsByTags', { 'tags': tags }); + + const _pathParams = { }; + const _queryParams = { + 'tags': tags }; + const _headerParams = { }; + const _body = undefined; + + return await fe.exec( + 'GET', '/v2' + '/pet/findByTags', + _pathParams, _queryParams, _headerParams, + undefined, + _body + ); +} + +/** + * Returns a single pet + * @summary Find pet by ID + * @param fe Callback interface that runs the fetch query + * @param petId ID of pet to return + * @response 200 [Pet] successful operation + * @response 400 [undefined] Invalid ID supplied + * @response 404 [undefined] Pet not found + */ +export public async function PetApi__getPetById(fe : ApiExecuterI, petId: int) : Promise> { + checkRequiredParams('getPetById', { 'petId': petId }); + + const _pathParams = { + 'petId': petId }; + const _queryParams = { }; + const _headerParams = { }; + const _body = undefined; + + return await fe.exec( + 'GET', '/v2' + '/pet/{petId}', + _pathParams, _queryParams, _headerParams, + undefined, + _body + ); +} + +/** + * + * @summary Update an existing pet + * @param fe Callback interface that runs the fetch query + * @param pet Pet object that needs to be added to the store + * @response 200 [Pet] successful operation + * @response 400 [undefined] Invalid ID supplied + * @response 404 [undefined] Pet not found + * @response 405 [undefined] Validation exception + */ +export public async function PetApi__updatePet(fe : ApiExecuterI, pet: Pet) : Promise> { + checkRequiredParams('updatePet', { 'pet': pet }); + + const _pathParams = { }; + const _queryParams = { }; + const _headerParams = { }; + const _body = cleanCopyBody(pet, 'id', 'category', 'name', 'photoUrls', 'tags', 'status'); + + return await fe.exec( + 'PUT', '/v2' + '/pet', + _pathParams, _queryParams, _headerParams, + undefined, + _body + ); +} + +/** + * + * @summary Updates a pet in the store with form data + * @param fe Callback interface that runs the fetch query + * @param petId ID of pet that needs to be updated + * @param name Updated name of the pet + * @param status Updated status of the pet + * @response 405 [undefined] Invalid input + */ +export public async function PetApi__updatePetWithForm(fe : ApiExecuterI, petId: int, name: string=, status: string=) : Promise> { + checkRequiredParams('updatePetWithForm', { 'petId': petId, }); + + const _pathParams = { + 'petId': petId }; + const _queryParams = { }; + const _headerParams = { }; + const _body = undefined; + + await fe.exec( + 'POST', '/v2' + '/pet/{petId}', + _pathParams, _queryParams, _headerParams, + undefined, + _body + ); +} + +/** + * + * @summary uploads an image + * @param fe Callback interface that runs the fetch query + * @param petId ID of pet to update + * @param additionalMetadata Additional data to pass to server + * @param file file to upload + * @response 200 [ApiResponse] successful operation + */ +export public async function PetApi__uploadFile(fe : ApiExecuterI, petId: int, additionalMetadata: string=, file: any=) : Promise { + checkRequiredParams('uploadFile', { 'petId': petId, }); + + const _pathParams = { + 'petId': petId }; + const _queryParams = { }; + const _headerParams = { }; + const _body = undefined; + + return await fe.exec( + 'POST', '/v2' + '/pet/{petId}/uploadImage', + _pathParams, _queryParams, _headerParams, + undefined, + _body + ); +} diff --git a/samples/client/petstore/n4js/api/StoreApi.n4js b/samples/client/petstore/n4js/api/StoreApi.n4js new file mode 100644 index 00000000000..1bb6d9f98e8 --- /dev/null +++ b/samples/client/petstore/n4js/api/StoreApi.n4js @@ -0,0 +1,115 @@ +/* + * OpenAPI Petstore + * This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters. + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +import { ApiExecuterI, ApiError, checkRequiredParams, cleanCopyBody } from "api/ApiHelper" +import { Order } from 'model/Order'; + + + + +/** + * For valid response try integer IDs with value < 1000. Anything above 1000 or nonintegers will generate API errors + * @summary Delete purchase order by ID + * @param fe Callback interface that runs the fetch query + * @param orderId ID of the order that needs to be deleted + * @response 400 [undefined] Invalid ID supplied + * @response 404 [undefined] Order not found + */ +export public async function StoreApi__deleteOrder(fe : ApiExecuterI, orderId: string) : Promise> { + checkRequiredParams('deleteOrder', { 'orderId': orderId }); + + const _pathParams = { + 'orderId': orderId }; + const _queryParams = { }; + const _headerParams = { }; + const _body = undefined; + + await fe.exec( + 'DELETE', '/v2' + '/store/order/{orderId}', + _pathParams, _queryParams, _headerParams, + undefined, + _body + ); +} + +/** + * Returns a map of status codes to quantities + * @summary Returns pet inventories by status + * @param fe Callback interface that runs the fetch query + * @response 200 [~Object+] successful operation + */ +export public async function StoreApi__getInventory(fe : ApiExecuterI, ) : Promise<~Object+, Object> { + checkRequiredParams('getInventory', { }); + + const _pathParams = { }; + const _queryParams = { }; + const _headerParams = { }; + const _body = undefined; + + return await fe.<~Object+, undefined>exec( + 'GET', '/v2' + '/store/inventory', + _pathParams, _queryParams, _headerParams, + undefined, + _body + ); +} + +/** + * For valid response try integer IDs with value <= 5 or > 10. Other values will generate exceptions + * @summary Find purchase order by ID + * @param fe Callback interface that runs the fetch query + * @param orderId ID of pet that needs to be fetched + * @response 200 [Order] successful operation + * @response 400 [undefined] Invalid ID supplied + * @response 404 [undefined] Order not found + */ +export public async function StoreApi__getOrderById(fe : ApiExecuterI, orderId: int) : Promise> { + checkRequiredParams('getOrderById', { 'orderId': orderId }); + + const _pathParams = { + 'orderId': orderId }; + const _queryParams = { }; + const _headerParams = { }; + const _body = undefined; + + return await fe.exec( + 'GET', '/v2' + '/store/order/{orderId}', + _pathParams, _queryParams, _headerParams, + undefined, + _body + ); +} + +/** + * + * @summary Place an order for a pet + * @param fe Callback interface that runs the fetch query + * @param order order placed for purchasing the pet + * @response 200 [Order] successful operation + * @response 400 [undefined] Invalid Order + */ +export public async function StoreApi__placeOrder(fe : ApiExecuterI, order: Order) : Promise> { + checkRequiredParams('placeOrder', { 'order': order }); + + const _pathParams = { }; + const _queryParams = { }; + const _headerParams = { }; + const _body = cleanCopyBody(order, 'id', 'petId', 'quantity', 'shipDate', 'status', 'complete'); + + return await fe.exec( + 'POST', '/v2' + '/store/order', + _pathParams, _queryParams, _headerParams, + undefined, + _body + ); +} diff --git a/samples/client/petstore/n4js/api/UserApi.n4js b/samples/client/petstore/n4js/api/UserApi.n4js new file mode 100644 index 00000000000..077fa6e30ab --- /dev/null +++ b/samples/client/petstore/n4js/api/UserApi.n4js @@ -0,0 +1,213 @@ +/* + * OpenAPI Petstore + * This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters. + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +import { ApiExecuterI, ApiError, checkRequiredParams, cleanCopyBody } from "api/ApiHelper" +import { User } from 'model/User'; + + + + +/** + * This can only be done by the logged in user. + * @summary Create user + * @param fe Callback interface that runs the fetch query + * @param user Created user object + * @response 0 [undefined] successful operation + */ +export public async function UserApi__createUser(fe : ApiExecuterI, user: User) : Promise { + checkRequiredParams('createUser', { 'user': user }); + + const _pathParams = { }; + const _queryParams = { }; + const _headerParams = { }; + const _body = cleanCopyBody(user, 'id', 'username', 'firstName', 'lastName', 'email', 'password', 'phone', 'userStatus'); + + await fe.exec( + 'POST', '/v2' + '/user', + _pathParams, _queryParams, _headerParams, + undefined, + _body + ); +} + +/** + * + * @summary Creates list of users with given input array + * @param fe Callback interface that runs the fetch query + * @param user List of user object + * @response 0 [undefined] successful operation + */ +export public async function UserApi__createUsersWithArrayInput(fe : ApiExecuterI, user: User[]) : Promise { + checkRequiredParams('createUsersWithArrayInput', { 'user': user }); + + const _pathParams = { }; + const _queryParams = { }; + const _headerParams = { }; + const _body = user; + + await fe.exec( + 'POST', '/v2' + '/user/createWithArray', + _pathParams, _queryParams, _headerParams, + undefined, + _body + ); +} + +/** + * + * @summary Creates list of users with given input array + * @param fe Callback interface that runs the fetch query + * @param user List of user object + * @response 0 [undefined] successful operation + */ +export public async function UserApi__createUsersWithListInput(fe : ApiExecuterI, user: User[]) : Promise { + checkRequiredParams('createUsersWithListInput', { 'user': user }); + + const _pathParams = { }; + const _queryParams = { }; + const _headerParams = { }; + const _body = user; + + await fe.exec( + 'POST', '/v2' + '/user/createWithList', + _pathParams, _queryParams, _headerParams, + undefined, + _body + ); +} + +/** + * This can only be done by the logged in user. + * @summary Delete user + * @param fe Callback interface that runs the fetch query + * @param username The name that needs to be deleted + * @response 400 [undefined] Invalid username supplied + * @response 404 [undefined] User not found + */ +export public async function UserApi__deleteUser(fe : ApiExecuterI, username: string) : Promise> { + checkRequiredParams('deleteUser', { 'username': username }); + + const _pathParams = { + 'username': username }; + const _queryParams = { }; + const _headerParams = { }; + const _body = undefined; + + await fe.exec( + 'DELETE', '/v2' + '/user/{username}', + _pathParams, _queryParams, _headerParams, + undefined, + _body + ); +} + +/** + * + * @summary Get user by user name + * @param fe Callback interface that runs the fetch query + * @param username The name that needs to be fetched. Use user1 for testing. + * @response 200 [User] successful operation + * @response 400 [undefined] Invalid username supplied + * @response 404 [undefined] User not found + */ +export public async function UserApi__getUserByName(fe : ApiExecuterI, username: string) : Promise> { + checkRequiredParams('getUserByName', { 'username': username }); + + const _pathParams = { + 'username': username }; + const _queryParams = { }; + const _headerParams = { }; + const _body = undefined; + + return await fe.exec( + 'GET', '/v2' + '/user/{username}', + _pathParams, _queryParams, _headerParams, + undefined, + _body + ); +} + +/** + * + * @summary Logs user into the system + * @param fe Callback interface that runs the fetch query + * @param username The user name for login + * @param password The password for login in clear text + * @response 200 [string] successful operation + * @response 400 [undefined] Invalid username/password supplied + */ +export public async function UserApi__loginUser(fe : ApiExecuterI, username: string, password: string) : Promise> { + checkRequiredParams('loginUser', { 'username': username, 'password': password }); + + const _pathParams = { }; + const _queryParams = { + 'username': username, + 'password': password }; + const _headerParams = { }; + const _body = undefined; + + return await fe.exec( + 'GET', '/v2' + '/user/login', + _pathParams, _queryParams, _headerParams, + undefined, + _body + ); +} + +/** + * + * @summary Logs out current logged in user session + * @param fe Callback interface that runs the fetch query + * @response 0 [undefined] successful operation + */ +export public async function UserApi__logoutUser(fe : ApiExecuterI, ) : Promise { + checkRequiredParams('logoutUser', { }); + + const _pathParams = { }; + const _queryParams = { }; + const _headerParams = { }; + const _body = undefined; + + await fe.exec( + 'GET', '/v2' + '/user/logout', + _pathParams, _queryParams, _headerParams, + undefined, + _body + ); +} + +/** + * This can only be done by the logged in user. + * @summary Updated user + * @param fe Callback interface that runs the fetch query + * @param username name that need to be deleted + * @param user Updated user object + * @response 400 [undefined] Invalid user supplied + * @response 404 [undefined] User not found + */ +export public async function UserApi__updateUser(fe : ApiExecuterI, username: string, user: User) : Promise> { + checkRequiredParams('updateUser', { 'username': username, 'user': user }); + + const _pathParams = { + 'username': username }; + const _queryParams = { }; + const _headerParams = { }; + const _body = cleanCopyBody(user, 'id', 'username', 'firstName', 'lastName', 'email', 'password', 'phone', 'userStatus'); + + await fe.exec( + 'PUT', '/v2' + '/user/{username}', + _pathParams, _queryParams, _headerParams, + undefined, + _body + ); +} diff --git a/samples/client/petstore/n4js/model/ApiResponse.n4jsd b/samples/client/petstore/n4js/model/ApiResponse.n4jsd new file mode 100644 index 00000000000..e1469ff41d5 --- /dev/null +++ b/samples/client/petstore/n4js/model/ApiResponse.n4jsd @@ -0,0 +1,21 @@ +/* + * OpenAPI Petstore + * This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters. + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +/** + * Describes the result of uploading an image resource + */ +export external public interface ~ApiResponse { + public code?: int; + public type?: string; + public message?: string; +} diff --git a/samples/client/petstore/n4js/model/Category.n4jsd b/samples/client/petstore/n4js/model/Category.n4jsd new file mode 100644 index 00000000000..5744a608344 --- /dev/null +++ b/samples/client/petstore/n4js/model/Category.n4jsd @@ -0,0 +1,20 @@ +/* + * OpenAPI Petstore + * This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters. + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +/** + * A category for a pet + */ +export external public interface ~Category { + public id?: int; + public name?: string; +} diff --git a/samples/client/petstore/n4js/model/Order.n4jsd b/samples/client/petstore/n4js/model/Order.n4jsd new file mode 100644 index 00000000000..6c595697890 --- /dev/null +++ b/samples/client/petstore/n4js/model/Order.n4jsd @@ -0,0 +1,28 @@ +/* + * OpenAPI Petstore + * This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters. + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +/** + * An order for a pets from the pet store + */ +export external public interface ~Order { + public id?: int; + public petId?: int; + public quantity?: int; + public shipDate?: string; + + /** + * Order Status + */ + public status?: "placed" | "approved" | "delivered"; + public complete?: boolean; +} diff --git a/samples/client/petstore/n4js/model/Pet.n4jsd b/samples/client/petstore/n4js/model/Pet.n4jsd new file mode 100644 index 00000000000..85ecd5e73a5 --- /dev/null +++ b/samples/client/petstore/n4js/model/Pet.n4jsd @@ -0,0 +1,30 @@ +/* + * OpenAPI Petstore + * This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters. + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { Category } from 'model/Category'; +import { Tag } from 'model/Tag'; + +/** + * A pet for sale in the pet store + */ +export external public interface ~Pet { + public id?: int; + public category?: Category; + public name: string; + public photoUrls: string[]; + public tags?: Tag[]; + + /** + * pet status in the store + */ + public status?: "available" | "pending" | "sold"; +} diff --git a/samples/client/petstore/n4js/model/Tag.n4jsd b/samples/client/petstore/n4js/model/Tag.n4jsd new file mode 100644 index 00000000000..47d9eafee4c --- /dev/null +++ b/samples/client/petstore/n4js/model/Tag.n4jsd @@ -0,0 +1,20 @@ +/* + * OpenAPI Petstore + * This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters. + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +/** + * A tag for a pet + */ +export external public interface ~Tag { + public id?: int; + public name?: string; +} diff --git a/samples/client/petstore/n4js/model/User.n4jsd b/samples/client/petstore/n4js/model/User.n4jsd new file mode 100644 index 00000000000..93f6dc76c26 --- /dev/null +++ b/samples/client/petstore/n4js/model/User.n4jsd @@ -0,0 +1,30 @@ +/* + * OpenAPI Petstore + * This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters. + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +/** + * A User who is purchasing from the pet store + */ +export external public interface ~User { + public id?: int; + public username?: string; + public firstName?: string; + public lastName?: string; + public email?: string; + public password?: string; + public phone?: string; + + /** + * User Status + */ + public userStatus?: int; +}