diff --git a/docs/generators/elixir.md b/docs/generators/elixir.md index b13adcbad45..bc1a7a9beb8 100644 --- a/docs/generators/elixir.md +++ b/docs/generators/elixir.md @@ -68,9 +68,22 @@ These options may be applied as additional-properties (cli) or configOptions (pl
  • __ENV__
  • __FILE__
  • __MODULE__
  • +
  • __struct__
  • +
  • after
  • +
  • and
  • +
  • catch
  • +
  • do
  • +
  • else
  • +
  • end
  • false
  • +
  • fn
  • +
  • in
  • nil
  • +
  • not
  • +
  • or
  • +
  • rescue
  • true
  • +
  • when
  • ## FEATURE SET diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ElixirClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ElixirClientCodegen.java index 690cdb7f7ec..670029e692f 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ElixirClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ElixirClientCodegen.java @@ -63,8 +63,7 @@ public class ElixirClientCodegen extends DefaultCodegen { "{:tesla, \"~> 1.7\"}", "{:jason, \"~> 1.4\"}", "{:ex_doc, \"~> 0.30\", only: :dev, runtime: false}", - "{:dialyxir, \"~> 1.3\", only: [:dev, :test], runtime: false}" - ); + "{:dialyxir, \"~> 1.3\", only: [:dev, :test], runtime: false}"); public ElixirClientCodegen() { super(); @@ -73,114 +72,117 @@ public class ElixirClientCodegen extends DefaultCodegen { .includeDocumentationFeatures(DocumentationFeature.Readme) .securityFeatures(EnumSet.of( SecurityFeature.OAuth2_Implicit, - SecurityFeature.BasicAuth - )) + SecurityFeature.BasicAuth)) .excludeGlobalFeatures( GlobalFeature.XMLStructureDefinitions, GlobalFeature.Callbacks, GlobalFeature.LinkObjects, - GlobalFeature.ParameterStyling - ) + GlobalFeature.ParameterStyling) .excludeSchemaSupportFeatures( - SchemaSupportFeature.Polymorphism - ) + SchemaSupportFeature.Polymorphism) .excludeParameterFeatures( - ParameterFeature.Cookie - ) + ParameterFeature.Cookie) .includeClientModificationFeatures( - ClientModificationFeature.BasePath - ) + ClientModificationFeature.BasePath) .includeDataTypeFeatures( - DataTypeFeature.AnyType - ) - ); + DataTypeFeature.AnyType)); // set the output folder here outputFolder = "generated-code/elixir"; /* - * Models. You can write model files using the modelTemplateFiles map. + * Models. You can write model files using the modelTemplateFiles map. * if you want to create one template for file, you can do so here. - * for multiple files for model, just put another entry in the `modelTemplateFiles` with + * for multiple files for model, just put another entry in the + * `modelTemplateFiles` with * a different extension */ modelTemplateFiles.put( "model.mustache", // the template to use - ".ex"); // the extension for each file to write + ".ex"); // the extension for each file to write /** - * Api classes. You can write classes for each Api file with the apiTemplateFiles map. - * as with models, add multiple entries with different extensions for multiple files per + * Api classes. You can write classes for each Api file with the + * apiTemplateFiles map. + * as with models, add multiple entries with different extensions for multiple + * files per * class */ apiTemplateFiles.put( - "api.mustache", // the template to use - ".ex"); // the extension for each file to write + "api.mustache", // the template to use + ".ex"); // the extension for each file to write /** - * Template Location. This is the location which templates will be read from. The generator + * Template Location. This is the location which templates will be read from. + * The generator * will use the resource stream to attempt to read the templates. */ templateDir = "elixir"; /** - * Reserved words. Override this with reserved words specific to your language - * Ref: https://github.com/itsgreggreg/elixir_quick_reference#reserved-words + * Reserved words. Override this with reserved words specific to your language + * Ref: https://hexdocs.pm/elixir/1.16.3/syntax-reference.html#reserved-words */ reservedWords = new HashSet<>( Arrays.asList( - "nil", "true", "false", + "nil", + "when", + "and", + "or", + "not", + "in", + "fn", + "do", + "end", + "catch", + "rescue", + "after", + "else", + "__struct__", "__MODULE__", "__FILE__", "__DIR__", "__ENV__", - "__CALLER__") - ); + "__CALLER__")); /** - * Additional Properties. These values can be passed to the templates and + * Additional Properties. These values can be passed to the templates and * are available in models, apis, and supporting files */ additionalProperties.put("apiVersion", apiVersion); /** - * Supporting Files. You can write single files for the generator with the - * entire object tree available. If the input file has a suffix of `.mustache - * it will be processed by the template engine. Otherwise, it will be copied + * Supporting Files. You can write single files for the generator with the + * entire object tree available. If the input file has a suffix of `.mustache + * it will be processed by the template engine. Otherwise, it will be copied */ - supportingFiles.add(new SupportingFile("README.md.mustache", // the input template or file - "", // the destination folder, relative `outputFolder` - "README.md") // the output file + supportingFiles.add(new SupportingFile("README.md.mustache", // the input template or file + "", // the destination folder, relative `outputFolder` + "README.md") // the output file ); supportingFiles.add(new SupportingFile("config.exs.mustache", "config", - "config.exs") - ); + "config.exs")); supportingFiles.add(new SupportingFile("runtime.exs.mustache", "config", - "runtime.exs") - ); + "runtime.exs")); supportingFiles.add(new SupportingFile("mix.exs.mustache", "", - "mix.exs") - ); + "mix.exs")); supportingFiles.add(new SupportingFile("formatter.exs", "", - ".formatter.exs") - ); + ".formatter.exs")); supportingFiles.add(new SupportingFile("test_helper.exs.mustache", "test", - "test_helper.exs") - ); + "test_helper.exs")); supportingFiles.add(new SupportingFile("gitignore.mustache", "", - ".gitignore") - ); + ".gitignore")); /** - * Language Specific Primitives. These types will not trigger imports by + * Language Specific Primitives. These types will not trigger imports by * the client generator */ languageSpecificPrimitives = new HashSet<>( @@ -196,12 +198,13 @@ public class ElixirClientCodegen extends DefaultCodegen { "AnyType", "Tuple", "PID", - "map()", // This is a workaround, since the DefaultCodeGen uses our elixir TypeSpec datetype to evaluate the primitive - "any()" - ) - ); + // This is a workaround, since the DefaultCodeGen uses our elixir TypeSpec + // datetype to evaluate the primitive + "map()", + "any()")); - // ref: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#data-types + // ref: + // https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#data-types typeMapping = new HashMap<>(); typeMapping.put("integer", "Integer"); typeMapping.put("long", "Integer"); @@ -223,7 +226,8 @@ public class ElixirClientCodegen extends DefaultCodegen { typeMapping.put("UUID", "String"); typeMapping.put("URI", "String"); - cliOptions.add(new CliOption(CodegenConstants.INVOKER_PACKAGE, "The main namespace to use for all classes. e.g. Yay.Pets")); + cliOptions.add(new CliOption(CodegenConstants.INVOKER_PACKAGE, + "The main namespace to use for all classes. e.g. Yay.Pets")); cliOptions.add(new CliOption("licenseHeader", "The license header to prepend to the top of all source files.")); cliOptions.add(new CliOption(CodegenConstants.PACKAGE_NAME, "Elixir package name (convention: lowercase).")); } @@ -240,7 +244,8 @@ public class ElixirClientCodegen extends DefaultCodegen { } /** - * Configures a friendly name for the generator. This will be used by the generator + * Configures a friendly name for the generator. This will be used by the + * generator * to select the library with the -g flag. * * @return the friendly name for the generator @@ -251,7 +256,7 @@ public class ElixirClientCodegen extends DefaultCodegen { } /** - * Returns human-friendly help for the generator. Provide the consumer with help + * Returns human-friendly help for the generator. Provide the consumer with help * tips, parameters here * * @return A string value for the help message @@ -323,7 +328,6 @@ public class ElixirClientCodegen extends DefaultCodegen { sourceFolder(), "request_builder.ex")); - supportingFiles.add(new SupportingFile("deserializer.ex.mustache", sourceFolder(), "deserializer.ex")); @@ -408,34 +412,39 @@ public class ElixirClientCodegen extends DefaultCodegen { } private String atomized(String text) { - StringBuilder atom = new StringBuilder(); - Matcher m = simpleAtomPattern.matcher(text); + StringBuilder atom = new StringBuilder(); + Matcher m = simpleAtomPattern.matcher(text); - atom.append(":"); + atom.append(":"); - if (!m.matches()) { - atom.append("\""); - } + if (!m.matches()) { + atom.append("\""); + } - atom.append(text); + atom.append(text); - if (!m.matches()) { - atom.append("\""); - } + if (!m.matches()) { + atom.append("\""); + } - return atom.toString(); + return atom.toString(); } - /** - * Escapes a reserved word as defined in the `reservedWords` array. Handle escaping - * those terms here. This logic is only called if a variable matches the reserved words + * Escapes a reserved word as defined in the `reservedWords` array. Handle + * escaping + * those terms here. This logic is only called if a variable matches the + * reserved words * * @return the escaped term */ @Override public String escapeReservedWord(String name) { - return "_" + name; // add an underscore to the name + String escapedName = name + "_var"; + + // Trim leading underscores in the event the name is already underscored + escapedName = escapedName.replaceAll("^_+", ""); + return escapedName; } private String sourceFolder() { @@ -447,7 +456,8 @@ public class ElixirClientCodegen extends DefaultCodegen { } /** - * Location to write model files. You can use the modelPackage() as defined when the class is + * Location to write model files. You can use the modelPackage() as defined when + * the class is * instantiated */ @Override @@ -456,7 +466,8 @@ public class ElixirClientCodegen extends DefaultCodegen { } /** - * Location to write api files. You can use the apiPackage() as defined when the class is + * Location to write api files. You can use the apiPackage() as defined when the + * class is * instantiated */ @Override @@ -529,20 +540,23 @@ public class ElixirClientCodegen extends DefaultCodegen { @Override public String toOperationId(String operationId) { - // throw exception if method name is empty (should not occur as an auto-generated method name will be used) + // throw exception if method name is empty (should not occur as an + // auto-generated method name will be used) if (StringUtils.isEmpty(operationId)) { throw new RuntimeException("Empty method name (operationId) not allowed"); } // method name cannot use reserved keyword, e.g. return if (isReservedWord(operationId)) { - LOGGER.warn("{} (reserved word) cannot be used as method name. Renamed to {}", operationId, underscore(sanitizeName("call_" + operationId))); + LOGGER.warn("{} (reserved word) cannot be used as method name. Renamed to {}", operationId, + underscore(sanitizeName("call_" + operationId))); return underscore(sanitizeName("call_" + operationId)); } // operationId starts with a number if (operationId.matches("^\\d.*")) { - LOGGER.warn("{} (starting with a number) cannot be used as method name. Renamed to {}", operationId, underscore(sanitizeName("call_" + operationId))); + LOGGER.warn("{} (starting with a number) cannot be used as method name. Renamed to {}", operationId, + underscore(sanitizeName("call_" + operationId))); operationId = "call_" + operationId; } @@ -550,10 +564,12 @@ public class ElixirClientCodegen extends DefaultCodegen { } /** - * Optional - type declaration. This is a String which is used by the templates to instantiate your - * types. There is typically special handling for different property types + * Optional - type declaration. This is a String which is used by the templates + * to instantiate your + * types. There is typically special handling for different property types * - * @return a string value used as the `dataType` field for model templates, `returnType` for api templates + * @return a string value used as the `dataType` field for model templates, + * `returnType` for api templates */ @Override public String getTypeDeclaration(Schema p) { @@ -603,8 +619,10 @@ public class ElixirClientCodegen extends DefaultCodegen { } /** - * Optional - OpenAPI type conversion. This is used to map OpenAPI types in a `Schema` into - * either language specific types via `typeMapping` or into complex models if there is not a mapping. + * Optional - OpenAPI type conversion. This is used to map OpenAPI types in a + * `Schema` into + * either language specific types via `typeMapping` or into complex models if + * there is not a mapping. * * @return a string value of the type or complex model for this property */ @@ -801,23 +819,10 @@ public class ElixirClientCodegen extends DefaultCodegen { if (exResponse.baseType == null) { returnEntry.append("nil"); } else if (exResponse.containerType == null) { // not container (array, map, set) - if (!exResponse.primitiveType) { - returnEntry.append(moduleName); - returnEntry.append(".Model."); - } - - translateBaseType(returnEntry, exResponse.baseType); + returnEntry.append(normalizeTypeName(exResponse.dataType, exResponse.primitiveType)); } else { - if (exResponse.containerType.equals("array") || - exResponse.containerType.equals("set")) { - returnEntry.append("list("); - if (!exResponse.primitiveType) { - returnEntry.append(moduleName); - returnEntry.append(".Model."); - } - - translateBaseType(returnEntry, exResponse.baseType); - returnEntry.append(")"); + if (exResponse.containerType.equals("array") || exResponse.containerType.equals("set")) { + returnEntry.append(exResponse.dataType); } else if (exResponse.containerType.equals("map")) { returnEntry.append("map()"); } @@ -833,6 +838,22 @@ public class ElixirClientCodegen extends DefaultCodegen { return sb.toString(); } + private String normalizeTypeName(String baseType, boolean isPrimitive) { + if (baseType == null) { + return "nil"; + } + if (isPrimitive || "String.t".equals(baseType)) { + return baseType; + } + if (!baseType.startsWith(moduleName + ".Model.")) { + baseType = moduleName + ".Model." + baseType; + } + if (!baseType.endsWith(".t")) { + baseType += ".t"; + } + return baseType; + } + private void buildTypespec(CodegenParameter param, StringBuilder sb) { if (param.dataType == null) { sb.append("nil"); @@ -846,26 +867,15 @@ public class ElixirClientCodegen extends DefaultCodegen { sb.append("%{optional(String.t) => "); buildTypespec(param.items, sb); sb.append("}"); - } else if (param.isPrimitiveType) { - // like `integer()`, `String.t` - sb.append(param.dataType); - } else if (param.isFile || param.isBinary) { - sb.append("String.t"); - } else if ("String.t".equals(param.dataType)) { - // uuid, password, etc - sb.append(param.dataType); } else { - // .Model..t - sb.append(moduleName); - sb.append(".Model."); - sb.append(param.dataType); - sb.append(".t"); + sb.append(normalizeTypeName(param.dataType, param.isPrimitiveType || param.isFile || param.isBinary)); } } private void buildTypespec(CodegenProperty property, StringBuilder sb) { if (property == null) { - LOGGER.error("CodegenProperty cannot be null. Please report the issue to https://github.com/openapitools/openapi-generator with the spec"); + LOGGER.error( + "CodegenProperty cannot be null. Please report the issue to https://github.com/openapitools/openapi-generator with the spec"); } else if (property.isArray) { sb.append("list("); buildTypespec(property.items, sb); @@ -874,14 +884,8 @@ public class ElixirClientCodegen extends DefaultCodegen { sb.append("%{optional(String.t) => "); buildTypespec(property.items, sb); sb.append("}"); - } else if (property.isPrimitiveType) { - sb.append(property.baseType); - sb.append(".t"); } else { - sb.append(moduleName); - sb.append(".Model."); - sb.append(property.baseType); - sb.append(".t"); + sb.append(normalizeTypeName(property.dataType, property.isPrimitiveType)); } } @@ -982,6 +986,8 @@ public class ElixirClientCodegen extends DefaultCodegen { } @Override - public GeneratorLanguage generatorLanguage() { return GeneratorLanguage.ELIXIR; } + public GeneratorLanguage generatorLanguage() { + return GeneratorLanguage.ELIXIR; + } } diff --git a/samples/client/petstore/elixir/lib/openapi_petstore/api/fake.ex b/samples/client/petstore/elixir/lib/openapi_petstore/api/fake.ex index b47e3ee8b50..a86a4160725 100644 --- a/samples/client/petstore/elixir/lib/openapi_petstore/api/fake.ex +++ b/samples/client/petstore/elixir/lib/openapi_petstore/api/fake.ex @@ -288,7 +288,7 @@ defmodule OpenapiPetstore.Api.Fake do - `{:ok, nil}` on success - `{:error, Tesla.Env.t}` on failure """ - @spec test_additional_properties_reference(Tesla.Env.client, %{optional(String.t) => AnyType.t}, keyword()) :: {:ok, nil} | {:error, Tesla.Env.t} + @spec test_additional_properties_reference(Tesla.Env.client, %{optional(String.t) => any()}, keyword()) :: {:ok, nil} | {:error, Tesla.Env.t} def test_additional_properties_reference(connection, request_body, _opts \\ []) do request = %{} diff --git a/samples/client/petstore/elixir/lib/openapi_petstore/api/pet.ex b/samples/client/petstore/elixir/lib/openapi_petstore/api/pet.ex index edbab8e9fb5..b1360cfe35e 100644 --- a/samples/client/petstore/elixir/lib/openapi_petstore/api/pet.ex +++ b/samples/client/petstore/elixir/lib/openapi_petstore/api/pet.ex @@ -93,7 +93,7 @@ defmodule OpenapiPetstore.Api.Pet do - `{:ok, [%Pet{}, ...]}` on success - `{:error, Tesla.Env.t}` on failure """ - @spec find_pets_by_status(Tesla.Env.client, list(String.t), keyword()) :: {:ok, nil} | {:ok, list(OpenapiPetstore.Model.Pet.t)} | {:error, Tesla.Env.t} + @spec find_pets_by_status(Tesla.Env.client, list(String.t), keyword()) :: {:ok, nil} | {:ok, [OpenapiPetstore.Model.Pet.t]} | {:error, Tesla.Env.t} def find_pets_by_status(connection, status, _opts \\ []) do request = %{} @@ -125,7 +125,7 @@ defmodule OpenapiPetstore.Api.Pet do - `{:ok, [%Pet{}, ...]}` on success - `{:error, Tesla.Env.t}` on failure """ - @spec find_pets_by_tags(Tesla.Env.client, list(String.t), keyword()) :: {:ok, nil} | {:ok, list(OpenapiPetstore.Model.Pet.t)} | {:error, Tesla.Env.t} + @spec find_pets_by_tags(Tesla.Env.client, list(String.t), keyword()) :: {:ok, nil} | {:ok, [OpenapiPetstore.Model.Pet.t]} | {:error, Tesla.Env.t} def find_pets_by_tags(connection, tags, _opts \\ []) do request = %{}