From 8511a533d602ace918c9f4ac5aa229dbf34ad37a Mon Sep 17 00:00:00 2001 From: William Cheng Date: Sat, 7 Sep 2024 17:18:06 +0800 Subject: [PATCH] add erlang-server-deprecated for fallback after refactoring (#19547) --- docs/generators.md | 1 + docs/generators/erlang-server-deprecated.md | 190 +++++++++ .../ErlangServerDeprecatedCodegen.java | 337 ++++++++++++++++ .../org.openapitools.codegen.CodegenConfig | 1 + .../erlang-server-deprecated/README.mustache | 57 +++ .../erlang-server-deprecated/api.mustache | 363 ++++++++++++++++++ .../erlang-server-deprecated/app.src.mustache | 19 + .../erlang-server-deprecated/auth.mustache | 52 +++ .../default_logic_handler.mustache | 32 ++ .../erlang-server-deprecated/handler.mustache | 252 ++++++++++++ .../logic_handler.mustache | 60 +++ .../erlang-server-deprecated/openapi.mustache | 1 + .../rebar.config.mustache | 6 + .../erlang-server-deprecated/router.mustache | 78 ++++ .../erlang-server-deprecated/server.mustache | 67 ++++ .../erlang-server-deprecated/utils.mustache | 173 +++++++++ 16 files changed, 1689 insertions(+) create mode 100644 docs/generators/erlang-server-deprecated.md create mode 100644 modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ErlangServerDeprecatedCodegen.java create mode 100644 modules/openapi-generator/src/main/resources/erlang-server-deprecated/README.mustache create mode 100644 modules/openapi-generator/src/main/resources/erlang-server-deprecated/api.mustache create mode 100644 modules/openapi-generator/src/main/resources/erlang-server-deprecated/app.src.mustache create mode 100644 modules/openapi-generator/src/main/resources/erlang-server-deprecated/auth.mustache create mode 100644 modules/openapi-generator/src/main/resources/erlang-server-deprecated/default_logic_handler.mustache create mode 100644 modules/openapi-generator/src/main/resources/erlang-server-deprecated/handler.mustache create mode 100644 modules/openapi-generator/src/main/resources/erlang-server-deprecated/logic_handler.mustache create mode 100644 modules/openapi-generator/src/main/resources/erlang-server-deprecated/openapi.mustache create mode 100644 modules/openapi-generator/src/main/resources/erlang-server-deprecated/rebar.config.mustache create mode 100644 modules/openapi-generator/src/main/resources/erlang-server-deprecated/router.mustache create mode 100644 modules/openapi-generator/src/main/resources/erlang-server-deprecated/server.mustache create mode 100644 modules/openapi-generator/src/main/resources/erlang-server-deprecated/utils.mustache diff --git a/docs/generators.md b/docs/generators.md index 1fe95d2ad93..6cf92be1945 100644 --- a/docs/generators.md +++ b/docs/generators.md @@ -89,6 +89,7 @@ The following generators are available: * [cpp-restbed-server-deprecated](generators/cpp-restbed-server-deprecated.md) * [csharp-functions](generators/csharp-functions.md) * [erlang-server](generators/erlang-server.md) +* [erlang-server-deprecated (deprecated)](generators/erlang-server-deprecated.md) * [fsharp-functions (beta)](generators/fsharp-functions.md) * [fsharp-giraffe-server (beta)](generators/fsharp-giraffe-server.md) * [go-echo-server (beta)](generators/go-echo-server.md) diff --git a/docs/generators/erlang-server-deprecated.md b/docs/generators/erlang-server-deprecated.md new file mode 100644 index 00000000000..b3f96ea6158 --- /dev/null +++ b/docs/generators/erlang-server-deprecated.md @@ -0,0 +1,190 @@ +--- +title: Documentation for the erlang-server-deprecated Generator +--- + +## METADATA + +| Property | Value | Notes | +| -------- | ----- | ----- | +| generator name | erlang-server-deprecated | pass this to the generate command after -g | +| generator stability | DEPRECATED | | +| generator type | SERVER | | +| generator language | Erlang | | +| generator default templating engine | mustache | | +| helpTxt | Generates an Erlang server library (deprecated) using OpenAPI Generator (https://openapi-generator.tech). By default, it will also generate service classes, which can be disabled with the `-Dnoservice` environment variable. | | + +## 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 | +| ------ | ----------- | ------ | ------- | +|openAPISpecName|Openapi Spec Name.| |openapi| +|packageName|Erlang package name (convention: lowercase).| |openapi| + +## 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 +|SignatureAuth|✗|OAS3 +|AWSV4Signature|✗|ToolingExtension + +### 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/ErlangServerDeprecatedCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ErlangServerDeprecatedCodegen.java new file mode 100644 index 00000000000..650951028cc --- /dev/null +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ErlangServerDeprecatedCodegen.java @@ -0,0 +1,337 @@ +/* + * Copyright 2018 OpenAPI-Generator Contributors (https://openapi-generator.tech) + * Copyright 2018 SmartBear Software + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.openapitools.codegen.languages; + +import lombok.Setter; +import org.openapitools.codegen.*; +import org.openapitools.codegen.meta.features.*; +import org.openapitools.codegen.model.ModelMap; +import org.openapitools.codegen.model.OperationMap; +import org.openapitools.codegen.model.OperationsMap; +import org.openapitools.codegen.meta.features.*; +import org.openapitools.codegen.meta.GeneratorMetadata; +import org.openapitools.codegen.meta.Stability; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.util.Arrays; +import java.util.EnumSet; +import java.util.List; +import java.util.Map; + +import static org.openapitools.codegen.utils.StringUtils.camelize; +import static org.openapitools.codegen.utils.StringUtils.underscore; + +public class ErlangServerDeprecatedCodegen extends DefaultCodegen implements CodegenConfig { + + private final Logger LOGGER = LoggerFactory.getLogger(ErlangServerDeprecatedCodegen.class); + + protected String apiVersion = "1.0.0"; + protected String apiPath = "src"; + @Setter protected String packageName = "openapi"; + @Setter protected String openApiSpecName = "openapi"; + + public ErlangServerDeprecatedCodegen() { + super(); + + generatorMetadata = GeneratorMetadata.newBuilder(generatorMetadata).stability(Stability.DEPRECATED).build(); + + modifyFeatureSet(features -> features + .includeDocumentationFeatures(DocumentationFeature.Readme) + .wireFormatFeatures(EnumSet.of(WireFormatFeature.JSON)) + .securityFeatures(EnumSet.of( + SecurityFeature.ApiKey, + SecurityFeature.OAuth2_Implicit + )) + .excludeGlobalFeatures( + GlobalFeature.XMLStructureDefinitions, + GlobalFeature.Callbacks, + GlobalFeature.LinkObjects, + GlobalFeature.ParameterStyling + ) + .excludeSchemaSupportFeatures( + SchemaSupportFeature.Polymorphism + ) + .excludeParameterFeatures( + ParameterFeature.Cookie + ) + ); + + // set the output folder here + outputFolder = "generated-code/erlang-server"; + + /** + * 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 + * a different extension + */ + modelTemplateFiles.clear(); + + /** + * 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( + "handler.mustache", // the template to use + ".erl"); // the extension for each file to write + + /** + * 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. + */ + embeddedTemplateDir = templateDir = "erlang-server-deprecated"; + + /** + * Reserved words. Override this with reserved words specific to your language + */ + setReservedWordsLowerCase( + Arrays.asList( + "after", "and", "andalso", "band", "begin", "bnot", "bor", "bsl", "bsr", "bxor", "case", + "catch", "cond", "div", "end", "fun", "if", "let", "not", "of", "or", "orelse", "receive", + "rem", "try", "when", "xor" + ) + ); + + instantiationTypes.clear(); + + typeMapping.clear(); + typeMapping.put("enum", "binary"); + typeMapping.put("date", "date"); + typeMapping.put("datetime", "datetime"); + typeMapping.put("boolean", "boolean"); + typeMapping.put("string", "binary"); + typeMapping.put("integer", "integer"); + typeMapping.put("int", "integer"); + typeMapping.put("float", "integer"); + typeMapping.put("long", "integer"); + typeMapping.put("double", "float"); + typeMapping.put("array", "list"); + typeMapping.put("map", "map"); + typeMapping.put("number", "integer"); + typeMapping.put("bigdecimal", "float"); + typeMapping.put("List", "list"); + typeMapping.put("object", "object"); + typeMapping.put("file", "file"); + typeMapping.put("binary", "binary"); + typeMapping.put("bytearray", "binary"); + typeMapping.put("byte", "binary"); + typeMapping.put("uuid", "binary"); + typeMapping.put("uri", "binary"); + typeMapping.put("password", "binary"); + + cliOptions.clear(); + cliOptions.add(new CliOption(CodegenConstants.PACKAGE_NAME, "Erlang package name (convention: lowercase).") + .defaultValue(this.packageName)); + + cliOptions.add(new CliOption(CodegenConstants.OPEN_API_SPEC_NAME, "Openapi Spec Name.") + .defaultValue(this.openApiSpecName)); + } + + @Override + public void processOpts() { + super.processOpts(); + + if (additionalProperties.containsKey(CodegenConstants.PACKAGE_NAME)) { + setPackageName((String) additionalProperties.get(CodegenConstants.PACKAGE_NAME)); + } else { + additionalProperties.put(CodegenConstants.PACKAGE_NAME, packageName); + } + + if (additionalProperties.containsKey(CodegenConstants.OPEN_API_SPEC_NAME)) { + setOpenApiSpecName((String) additionalProperties.get(CodegenConstants.OPEN_API_SPEC_NAME)); + } else { + additionalProperties.put(CodegenConstants.OPEN_API_SPEC_NAME, openApiSpecName); + } + + /** + * Additional Properties. These values can be passed to the templates and + * are available in models, apis, and supporting files + */ + additionalProperties.put("apiVersion", apiVersion); + additionalProperties.put("apiPath", apiPath); + /** + * 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("rebar.config.mustache", "", "rebar.config")); + supportingFiles.add(new SupportingFile("app.src.mustache", "", "src" + File.separator + this.packageName + ".app.src")); + supportingFiles.add(new SupportingFile("router.mustache", "", toSourceFilePath("router", "erl"))); + supportingFiles.add(new SupportingFile("api.mustache", "", toSourceFilePath("api", "erl"))); + supportingFiles.add(new SupportingFile("server.mustache", "", toSourceFilePath("server", "erl"))); + supportingFiles.add(new SupportingFile("utils.mustache", "", toSourceFilePath("utils", "erl"))); + supportingFiles.add(new SupportingFile("auth.mustache", "", toSourceFilePath("auth", "erl"))); + supportingFiles.add(new SupportingFile("openapi.mustache", "", toPrivFilePath(this.openApiSpecName, "json"))); + supportingFiles.add(new SupportingFile("default_logic_handler.mustache", "", toSourceFilePath("default_logic_handler", "erl"))); + supportingFiles.add(new SupportingFile("logic_handler.mustache", "", toSourceFilePath("logic_handler", "erl"))); + supportingFiles.add(new SupportingFile("README.mustache", "", "README.md") + .doNotOverwrite()); + } + + @Override + public String apiPackage() { + return apiPath; + } + + /** + * Configures the type of generator. + * + * @return the CodegenType for this generator + * @see org.openapitools.codegen.CodegenType + */ + @Override + public CodegenType getTag() { + return CodegenType.SERVER; + } + + /** + * 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 + */ + @Override + public String getName() { + return "erlang-server-deprecated"; + } + + /** + * Returns human-friendly help for the generator. Provide the consumer with help + * tips, parameters here + * + * @return A string value for the help message + */ + @Override + public String getHelp() { + return "Generates an Erlang server library (deprecated) using OpenAPI Generator (https://openapi-generator.tech). By default, " + + "it will also generate service classes, which can be disabled with the `-Dnoservice` environment variable."; + } + + @Override + public String toApiName(String name) { + if (name.length() == 0) { + return this.packageName + "_default_handler"; + } + return this.packageName + "_" + underscore(name) + "_handler"; + } + + /** + * 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) { + if (this.reservedWordsMappings().containsKey(name)) { + return this.reservedWordsMappings().get(name); + } + return "_" + name; + } + + /** + * Location to write api files. You can use the apiPackage() as defined when the class is + * instantiated + */ + @Override + public String apiFileFolder() { + return outputFolder + File.separator + apiPackage().replace('.', File.separatorChar); + } + + @Override + public String toModelName(String name) { + return camelize(toModelFilename(name)); + } + + @Override + public String toOperationId(String operationId) { + // 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, camelize(sanitizeName("call_" + operationId))); + operationId = "call_" + operationId; + } + + return camelize(operationId); + } + + @Override + public String toApiFilename(String name) { + return toHandlerName(name); + } + + @Override + public OperationsMap postProcessOperationsWithModels(OperationsMap objs, List allModels) { + OperationMap operations = objs.getOperations(); + List operationList = operations.getOperation(); + for (CodegenOperation op : operationList) { + if (op.path != null) { + op.path = op.path.replaceAll("\\{(.*?)\\}", ":$1"); + } + } + return objs; + } + + @Override + public Map postProcessSupportingFileData(Map objs) { + generateJSONSpecFile(objs); + return super.postProcessSupportingFileData(objs); + } + + protected String toHandlerName(String name) { + return toModuleName(name) + "_handler"; + } + + protected String toModuleName(String name) { + return this.packageName + "_" + underscore(name.replaceAll("-", "_")); + } + + protected String toSourceFilePath(String name, String extension) { + return "src" + File.separator + toModuleName(name) + "." + extension; + } + + protected String toIncludeFilePath(String name, String extension) { + return "include" + File.separator + toModuleName(name) + "." + extension; + } + + protected String toPrivFilePath(String name, String extension) { + return "priv" + File.separator + name + "." + extension; + } + + @Override + public String escapeQuotationMark(String input) { + // remove ' to avoid code injection + return input.replace("'", ""); + } + + @Override + public String escapeUnsafeCharacters(String input) { + // ref: http://stackoverflow.com/a/30421295/677735 + return input.replace("-ifdef", "- if def").replace("-endif", "- end if"); + } + + @Override + public String addRegularExpressionDelimiter(String pattern) { + return pattern; + } + + @Override + public GeneratorLanguage generatorLanguage() { return GeneratorLanguage.ERLANG; } +} 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 ba2d139af98..00a51f289ab 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 @@ -30,6 +30,7 @@ org.openapitools.codegen.languages.ElmClientCodegen org.openapitools.codegen.languages.ErlangClientCodegen org.openapitools.codegen.languages.ErlangProperCodegen org.openapitools.codegen.languages.ErlangServerCodegen +org.openapitools.codegen.languages.ErlangServerDeprecatedCodegen org.openapitools.codegen.languages.FsharpFunctionsServerCodegen org.openapitools.codegen.languages.FsharpGiraffeServerCodegen org.openapitools.codegen.languages.GoClientCodegen diff --git a/modules/openapi-generator/src/main/resources/erlang-server-deprecated/README.mustache b/modules/openapi-generator/src/main/resources/erlang-server-deprecated/README.mustache new file mode 100644 index 00000000000..5d36ca07576 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/erlang-server-deprecated/README.mustache @@ -0,0 +1,57 @@ +# OpenAPI server library for Erlang + +## Overview + +An Erlang server stub generated by [OpenAPI Generator](https://openapi-generator.tech) given an OpenAPI spec. + +Dependency: [Cowboy](https://github.com/ninenines/cowboy) + +## Prerequisites + +TODO + +## Getting started +Use erlang-server with erlang.mk + + 1, Create an application by using erlang.mk + $ mkdir http_server + $ cd http_server + $ wget https://erlang.mk/erlang.mk + $ make -f erlang.mk bootstrap bootstrap-rel + $ make run + + 2, Modify the Makefile in the http_server directory to the following to introduce the dependency library: + PROJECT = http_server + PROJECT_DESCRIPTION = New project + PROJECT_VERSION = 0.1.0 + + DEPS = cowboy jesse jsx + dep_cowboy_commit = 2.5.0 + dep_jesse_commit = 1.5.2 + dep_jsx_commit = 2.9.0 + DEP_PLUGINS = cowboy jesse jsx + + PACKAGES += rfc3339 + pkg_rfc3339_name = rfc3339 + pkg_rfc3339_description = an erlang/elixir rfc3339 lib + pkg_rfc3339_homepage = https://github.com/talentdeficit/rfc3339 + pkg_rfc3339_fetch = git + pkg_rfc3339_repo = https://github.com/talentdeficit/rfc3339 + pkg_rfc3339_commit = master + + include erlang.mk + + 3, Generate erlang-server project using openapi-generator + https://github.com/OpenAPITools/openapi-generator#2---getting-started + + 4, Copy erlang-server file to http_server project,Don't forget the 'priv' folder. + + 5, Start in the http_server project: + 1, Introduce the following line in the http_server_app:start(_Type, _Args) function + openapi_server:start(http_server, #{ip=>{127,0,0,1}, port=>8080, net_opts=>[]}) + 2, Compilation http_server project + $ make + 3, Start erlang virtual machine + $erl -pa ./deps/cowboy/ebin -pa ./deps/cowlib/ebin -pa ./deps/ranch/ebin -pa ./deps/jsx/ebin -pa ./deps/jesse/ebin -pa ./deps/rfc3339/ebin -pa ./ebin + 4, Start project + application:ensure_all_started(http_server). diff --git a/modules/openapi-generator/src/main/resources/erlang-server-deprecated/api.mustache b/modules/openapi-generator/src/main/resources/erlang-server-deprecated/api.mustache new file mode 100644 index 00000000000..b97e4a2c5ec --- /dev/null +++ b/modules/openapi-generator/src/main/resources/erlang-server-deprecated/api.mustache @@ -0,0 +1,363 @@ +-module({{packageName}}_api). + +-export([request_params/1]). +-export([request_param_info/2]). +-export([populate_request/3]). +-export([validate_response/4]). +%% exported to silence openapi complains +-export([get_value/3, validate_response_body/4]). + +-type operation_id() :: atom(). +-type request_param() :: atom(). + +-export_type([operation_id/0]). + +-spec request_params(OperationID :: operation_id()) -> [Param :: request_param()]. +{{#apiInfo}}{{#apis}} +{{#operations}}{{#operation}} +request_params('{{operationId}}') -> + [{{#allParams}}{{^isBodyParam}} + '{{baseName}}'{{/isBodyParam}}{{#isBodyParam}} + '{{dataType}}'{{/isBodyParam}}{{^-last}},{{/-last}}{{/allParams}} + ]; +{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}} +request_params(_) -> + error(unknown_operation). + +-type rule() :: + {type, 'binary'} | + {type, 'integer'} | + {type, 'float'} | + {type, 'binary'} | + {type, 'boolean'} | + {type, 'date'} | + {type, 'datetime'} | + {enum, [atom()]} | + {max, Max :: number()} | + {exclusive_max, Max :: number()} | + {min, Min :: number()} | + {exclusive_min, Min :: number()} | + {max_length, MaxLength :: integer()} | + {min_length, MaxLength :: integer()} | + {pattern, Pattern :: string()} | + schema | + required | + not_required. + +-spec request_param_info(OperationID :: operation_id(), Name :: request_param()) -> #{ + source => qs_val | binding | header | body, + rules => [rule()] +}. + +{{#apiInfo}}{{#apis}} +{{#operations}}{{#operation}}{{#allParams}} +request_param_info('{{operationId}}', {{^isBodyParam}}'{{baseName}}'{{/isBodyParam}}{{#isBodyParam}}'{{dataType}}'{{/isBodyParam}}) -> + #{ + source => {{#isQueryParam}}qs_val{{/isQueryParam}} {{#isPathParam}}binding{{/isPathParam}} {{#isHeaderParam}}header{{/isHeaderParam}}{{#isBodyParam}}body{{/isBodyParam}}{{#isFormParam}}body{{/isFormParam}}, + rules => [{{#isString}} + {type, 'binary'},{{/isString}}{{#isInteger}} + {type, 'integer'},{{/isInteger}}{{#isLong}} + {type, 'integer'},{{/isLong}}{{#isFloat}} + {type, 'float'},{{/isFloat}}{{#isDouble}} + {type, 'float'},{{/isDouble}}{{#isByteArray}} + {type, 'binary'},{{/isByteArray}}{{#isBinary}} + {type, 'binary'},{{/isBinary}}{{#isBoolean}} + {type, 'boolean'},{{/isBoolean}}{{#isDate}} + {type, 'date'},{{/isDate}}{{#isDateTime}} + {type, 'datetime'},{{/isDateTime}}{{#isEnum}} + {enum, [{{#allowableValues}}{{#values}}'{{.}}'{{^-last}}, {{/-last}}{{/values}}{{/allowableValues}}] },{{/isEnum}}{{#maximum}} + {max, {{maximum}} }, {{/maximum}}{{#exclusiveMaximum}} + {exclusive_max, {{exclusiveMaximum}} },{{/exclusiveMaximum}}{{#minimum}} + {min, {{minimum}} },{{/minimum}}{{#exclusiveMinimum}} + {exclusive_min, {{exclusiveMinimum}} },{{/exclusiveMinimum}}{{#maxLength}} + {max_length, {{maxLength}} },{{/maxLength}}{{#minLength}} + {min_length, {{minLength}} },{{/minLength}}{{#pattern}} + {pattern, "{{{pattern}}}" },{{/pattern}}{{#isBodyParam}} + schema,{{/isBodyParam}}{{#required}} + required{{/required}}{{^required}} + not_required{{/required}} + ] + }; +{{/allParams}}{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}} +request_param_info(OperationID, Name) -> + error({unknown_param, OperationID, Name}). + +-spec populate_request( + OperationID :: operation_id(), + Req :: cowboy_req:req(), + ValidatorState :: jesse_state:state() +) -> + {ok, Model :: #{}, Req :: cowboy_req:req()} | + {error, Reason :: any(), Req :: cowboy_req:req()}. + +populate_request(OperationID, Req, ValidatorState) -> + Params = request_params(OperationID), + populate_request_params(OperationID, Params, Req, ValidatorState, #{}). + +populate_request_params(_, [], Req, _, Model) -> + {ok, Model, Req}; + +populate_request_params(OperationID, [FieldParams | T], Req0, ValidatorState, Model) -> + case populate_request_param(OperationID, FieldParams, Req0, ValidatorState) of + {ok, K, V, Req} -> + populate_request_params(OperationID, T, Req, ValidatorState, maps:put(K, V, Model)); + Error -> + Error + end. + +populate_request_param(OperationID, Name, Req0, ValidatorState) -> + #{rules := Rules, source := Source} = request_param_info(OperationID, Name), + case get_value(Source, Name, Req0) of + {error, Reason, Req} -> + {error, Reason, Req}; + {Value, Req} -> + case prepare_param(Rules, Name, Value, ValidatorState) of + {ok, Result} -> {ok, Name, Result, Req}; + {error, Reason} -> + {error, Reason, Req} + end + end. + +-spec validate_response( + OperationID :: operation_id(), + Code :: 200..599, + Body :: jesse:json_term(), + ValidatorState :: jesse_state:state() +) -> ok | no_return(). +{{#apiInfo}}{{#apis}} +{{#operations}}{{#operation}} +{{#responses}} +validate_response('{{operationId}}', {{code}}, Body, ValidatorState) -> + validate_response_body('{{dataType}}', '{{baseType}}', Body, ValidatorState); +{{/responses}} +{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}} + +validate_response(_OperationID, _Code, _Body, _ValidatorState) -> + ok. + +validate_response_body('list', ReturnBaseType, Body, ValidatorState) -> + [ + validate(schema, ReturnBaseType, Item, ValidatorState) + || Item <- Body]; + +validate_response_body(_, ReturnBaseType, Body, ValidatorState) -> + validate(schema, ReturnBaseType, Body, ValidatorState). + +%%% +validate(Rule = required, Name, Value, _ValidatorState) -> + case Value of + undefined -> validation_error(Rule, Name); + _ -> ok + end; + +validate(not_required, _Name, _Value, _ValidatorState) -> + ok; + +validate(_, _Name, undefined, _ValidatorState) -> + ok; + +validate(Rule = {type, 'integer'}, Name, Value, _ValidatorState) -> + try + {ok, {{packageName}}_utils:to_int(Value)} + catch + error:badarg -> + validation_error(Rule, Name) + end; + +validate(Rule = {type, 'float'}, Name, Value, _ValidatorState) -> + try + {ok, {{packageName}}_utils:to_float(Value)} + catch + error:badarg -> + validation_error(Rule, Name) + end; + +validate(Rule = {type, 'binary'}, Name, Value, _ValidatorState) -> + case is_binary(Value) of + true -> ok; + false -> validation_error(Rule, Name) + end; + +validate(_Rule = {type, 'boolean'}, _Name, Value, _ValidatorState) when is_boolean(Value) -> + {ok, Value}; + +validate(Rule = {type, 'boolean'}, Name, Value, _ValidatorState) -> + V = binary_to_lower(Value), + try + case binary_to_existing_atom(V, utf8) of + B when is_boolean(B) -> {ok, B}; + _ -> validation_error(Rule, Name) + end + catch + error:badarg -> + validation_error(Rule, Name) + end; + +validate(Rule = {type, 'date'}, Name, Value, _ValidatorState) -> + case is_binary(Value) of + true -> ok; + false -> validation_error(Rule, Name) + end; + +validate(Rule = {type, 'datetime'}, Name, Value, _ValidatorState) -> + case is_binary(Value) of + true -> ok; + false -> validation_error(Rule, Name) + end; + +validate(Rule = {enum, Values}, Name, Value, _ValidatorState) -> + try + FormattedValue = erlang:binary_to_existing_atom(Value, utf8), + case lists:member(FormattedValue, Values) of + true -> {ok, FormattedValue}; + false -> validation_error(Rule, Name) + end + catch + error:badarg -> + validation_error(Rule, Name) + end; + +validate(Rule = {max, Max}, Name, Value, _ValidatorState) -> + case Value =< Max of + true -> ok; + false -> validation_error(Rule, Name) + end; + +validate(Rule = {exclusive_max, ExclusiveMax}, Name, Value, _ValidatorState) -> + case Value > ExclusiveMax of + true -> ok; + false -> validation_error(Rule, Name) + end; + +validate(Rule = {min, Min}, Name, Value, _ValidatorState) -> + case Value >= Min of + true -> ok; + false -> validation_error(Rule, Name) + end; + +validate(Rule = {exclusive_min, ExclusiveMin}, Name, Value, _ValidatorState) -> + case Value =< ExclusiveMin of + true -> ok; + false -> validation_error(Rule, Name) + end; + +validate(Rule = {max_length, MaxLength}, Name, Value, _ValidatorState) -> + case size(Value) =< MaxLength of + true -> ok; + false -> validation_error(Rule, Name) + end; + +validate(Rule = {min_length, MinLength}, Name, Value, _ValidatorState) -> + case size(Value) >= MinLength of + true -> ok; + false -> validation_error(Rule, Name) + end; + +validate(Rule = {pattern, Pattern}, Name, Value, _ValidatorState) -> + {ok, MP} = re:compile(Pattern), + case re:run(Value, MP) of + {match, _} -> ok; + _ -> validation_error(Rule, Name) + end; + +validate(Rule = schema, Name, Value, ValidatorState) -> + Definition = list_to_binary("#/components/schemas/" ++ {{packageName}}_utils:to_list(Name)), + try + _ = validate_with_schema(Value, Definition, ValidatorState), + ok + catch + throw:[{schema_invalid, _, Error} | _] -> + Info = #{ + type => schema_invalid, + error => Error + }, + validation_error(Rule, Name, Info); + throw:[{data_invalid, Schema, Error, _, Path} | _] -> + Info = #{ + type => data_invalid, + error => Error, + schema => Schema, + path => Path + }, + validation_error(Rule, Name, Info) + end; + +validate(Rule, Name, _Value, _ValidatorState) -> + error_logger:info_msg("Can't validate ~p with ~p", [Name, Rule]), + error({unknown_validation_rule, Rule}). + +-spec validation_error(Rule :: any(), Name :: any()) -> no_return(). + +validation_error(ViolatedRule, Name) -> + validation_error(ViolatedRule, Name, #{}). + +-spec validation_error(Rule :: any(), Name :: any(), Info :: #{}) -> no_return(). + +validation_error(ViolatedRule, Name, Info) -> + throw({wrong_param, Name, ViolatedRule, Info}). + +-spec get_value(body | qs_val | header | binding, Name :: any(), Req0 :: cowboy_req:req()) -> + {Value :: any(), Req :: cowboy_req:req()} | + {error, Reason :: any(), Req :: cowboy_req:req()}. +get_value(body, _Name, Req0) -> + {ok, Body, Req} = cowboy_req:read_body(Req0), + case prepare_body(Body) of + {error, Reason} -> + {error, Reason, Req}; + Value -> + {Value, Req} + end; + +get_value(qs_val, Name, Req) -> + QS = cowboy_req:parse_qs(Req), + Value = {{packageName}}_utils:get_opt({{packageName}}_utils:to_qs(Name), QS), + {Value, Req}; + +get_value(header, Name, Req) -> + Headers = cowboy_req:headers(Req), + Value = maps:get({{packageName}}_utils:to_header(Name), Headers, undefined), + {Value, Req}; + +get_value(binding, Name, Req) -> + Value = cowboy_req:binding({{packageName}}_utils:to_binding(Name), Req), + {Value, Req}. + +prepare_body(Body) -> + case Body of + <<"">> -> <<"">>; + _ -> + try + jsx:decode(Body, [return_maps]) + catch + error:_ -> + {error, {invalid_body, not_json, Body}} + end + end. + +validate_with_schema(Body, Definition, ValidatorState) -> + jesse_schema_validator:validate_with_state( + [{<<"$ref">>, Definition}], + Body, + ValidatorState + ). + +prepare_param(Rules, Name, Value, ValidatorState) -> + try + Result = lists:foldl( + fun(Rule, Acc) -> + case validate(Rule, Name, Acc, ValidatorState) of + ok -> Acc; + {ok, Prepared} -> Prepared + end + end, + Value, + Rules + ), + {ok, Result} + catch + throw:Reason -> + {error, Reason} + end. + +binary_to_lower(V) when is_binary(V) -> + list_to_binary(string:to_lower({{packageName}}_utils:to_list(V))). diff --git a/modules/openapi-generator/src/main/resources/erlang-server-deprecated/app.src.mustache b/modules/openapi-generator/src/main/resources/erlang-server-deprecated/app.src.mustache new file mode 100644 index 00000000000..a90c4e7cae7 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/erlang-server-deprecated/app.src.mustache @@ -0,0 +1,19 @@ +{application, {{packageName}}, [ + {description, {{#appDescription}}"{{.}}"{{/appDescription}}{{^appDescription}}"OpenAPI rest server library"{{/appDescription}}}, + {vsn, "{{apiVersion}}"}, + {registered, []}, + {applications, [ + kernel, + stdlib, + ssl, + inets, + jsx, + jesse, + cowboy + ]}, + {env, [ + ]}, + {modules, []}, + {licenses, [{{#licenseInfo}}"{{.}}"{{/licenseInfo}}]}, + {links, [{{#infoUrl}}"{{.}}"{{/infoUrl}}]} +]}. diff --git a/modules/openapi-generator/src/main/resources/erlang-server-deprecated/auth.mustache b/modules/openapi-generator/src/main/resources/erlang-server-deprecated/auth.mustache new file mode 100644 index 00000000000..3159e352a9c --- /dev/null +++ b/modules/openapi-generator/src/main/resources/erlang-server-deprecated/auth.mustache @@ -0,0 +1,52 @@ +-module({{packageName}}_auth). + +-export([authorize_api_key/5]). + +-spec authorize_api_key( + LogicHandler :: atom(), + OperationID :: {{packageName}}_api:operation_id(), + From :: header | qs_val, + KeyParam :: iodata() | atom(), + Req ::cowboy_req:req() +)-> {true, Context :: #{binary() => any()}, Req ::cowboy_req:req()} | + {false, AuthHeader :: binary(), Req ::cowboy_req:req()}. + +authorize_api_key(LogicHandler, OperationID, From, KeyParam, Req0) -> + {ApiKey, Req} = get_api_key(From, KeyParam, Req0), + case ApiKey of + undefined -> + AuthHeader = <<"">>, + {false, AuthHeader, Req}; + _ -> + Result = {{packageName}}_logic_handler:authorize_api_key( + LogicHandler, + OperationID, + ApiKey + ), + case Result of + {{#authMethods}} + {{#isApiKey}} + {true, Context} -> + {true, Context, Req}; + {{/isApiKey}} + {{/authMethods}} + false -> + AuthHeader = <<"">>, + {false, AuthHeader, Req} + end + end. + +get_api_key(header, KeyParam, Req) -> + Headers = cowboy_req:headers(Req), + { + maps:get( + {{packageName}}_utils:to_header(KeyParam), + Headers, + undefined + ), + Req + }; + +get_api_key(qs_val, KeyParam, Req) -> + QS = cowboy_req:parse_qs(Req), + { {{packageName}}_utils:get_opt(KeyParam, QS), Req}. diff --git a/modules/openapi-generator/src/main/resources/erlang-server-deprecated/default_logic_handler.mustache b/modules/openapi-generator/src/main/resources/erlang-server-deprecated/default_logic_handler.mustache new file mode 100644 index 00000000000..1b39ed1d75a --- /dev/null +++ b/modules/openapi-generator/src/main/resources/erlang-server-deprecated/default_logic_handler.mustache @@ -0,0 +1,32 @@ +-module({{packageName}}_default_logic_handler). + +-behaviour({{packageName}}_logic_handler). + +-export([handle_request/3]). +{{#authMethods}} + {{#isApiKey}} +-export([authorize_api_key/2]). + {{/isApiKey}} +{{/authMethods}} + +{{#authMethods}} + {{#isApiKey}} +-spec authorize_api_key(OperationID :: {{packageName}}_api:operation_id(), ApiKey :: binary()) -> {true, #{}}. + +authorize_api_key(_, _) -> {true, #{}}. + {{/isApiKey}} +{{/authMethods}} + +-spec handle_request( + OperationID :: {{packageName}}_api:operation_id(), + Req :: cowboy_req:req(), + Context :: #{} +) -> + {Status :: cowboy:http_status(), Headers :: cowboy:http_headers(), Body :: jsx:json_term()}. + +handle_request(OperationID, Req, Context) -> + error_logger:error_msg( + "Got not implemented request to process: ~p~n", + [{OperationID, Req, Context}] + ), + {501, #{}, #{}}. diff --git a/modules/openapi-generator/src/main/resources/erlang-server-deprecated/handler.mustache b/modules/openapi-generator/src/main/resources/erlang-server-deprecated/handler.mustache new file mode 100644 index 00000000000..70756b2549f --- /dev/null +++ b/modules/openapi-generator/src/main/resources/erlang-server-deprecated/handler.mustache @@ -0,0 +1,252 @@ +%% basic handler +-module({{classname}}). + +%% Cowboy REST callbacks +-export([allowed_methods/2]). +-export([init/2]). +-export([allow_missing_post/2]). +-export([content_types_accepted/2]). +-export([content_types_provided/2]). +-export([delete_resource/2]). +-export([is_authorized/2]). +-export([known_content_type/2]). +-export([malformed_request/2]). +-export([valid_content_headers/2]). +-export([valid_entity_length/2]). + +%% Handlers +-export([handle_request_json/2]). + +-record(state, { + operation_id :: {{packageName}}_api:operation_id(), + logic_handler :: atom(), + validator_state :: jesse_state:state(), + context=#{} :: #{} +}). + +-type state() :: state(). + +-spec init(Req :: cowboy_req:req(), Opts :: {{packageName}}_router:init_opts()) -> + {cowboy_rest, Req :: cowboy_req:req(), State :: state()}. + +init(Req, {Operations, LogicHandler, ValidatorMod}) -> + Method = cowboy_req:method(Req), + OperationID = maps:get(Method, Operations, undefined), + + ValidatorState = ValidatorMod:get_validator_state(), + + error_logger:info_msg("Attempt to process operation: ~p", [OperationID]), + + State = #state{ + operation_id = OperationID, + logic_handler = LogicHandler, + validator_state = ValidatorState + }, + {cowboy_rest, Req, State}. + +-spec allowed_methods(Req :: cowboy_req:req(), State :: state()) -> + {Value :: [binary()], Req :: cowboy_req:req(), State :: state()}. + +{{#operations}}{{#operation}} +allowed_methods( + Req, + State = #state{ + operation_id = '{{operationId}}' + } +) -> + {[<<"{{httpMethod}}">>], Req, State}; +{{/operation}}{{/operations}} +allowed_methods(Req, State) -> + {[], Req, State}. + +-spec is_authorized(Req :: cowboy_req:req(), State :: state()) -> + { + Value :: true | {false, AuthHeader :: iodata()}, + Req :: cowboy_req:req(), + State :: state() + }. +{{#operations}} +{{#operation}} +{{#authMethods}} +is_authorized( + Req0, + State = #state{ + operation_id = '{{operationId}}' = OperationID, + logic_handler = LogicHandler + } +) -> + {{#isApiKey}} + From = {{#isKeyInQuery}}qs_val{{/isKeyInQuery}}{{#isKeyInHeader}}header{{/isKeyInHeader}}, + Result = {{packageName}}_auth:authorize_api_key( + LogicHandler, + OperationID, + From, + "{{keyParamName}}", + Req0 + ), + case Result of + {true, Context, Req} -> {true, Req, State#state{context = Context}}; + {false, AuthHeader, Req} -> {{false, AuthHeader}, Req, State} + end; + {{/isApiKey}} + {{#isOAuth}} + From = header, + Result = {{packageName}}_auth:authorize_api_key( + LogicHandler, + OperationID, + From, + "Authorization", + Req0 + ), + case Result of + {true, Context, Req} -> {true, Req, State#state{context = Context}}; + {false, AuthHeader, Req} -> {{false, AuthHeader}, Req, State} + end; + {{/isOAuth}} +{{/authMethods}} +{{/operation}} +{{/operations}} +{{^authMethods}} +is_authorized(Req, State) -> + {true, Req, State}. +{{/authMethods}} +{{#authMethods}} +is_authorized(Req, State) -> + {{false, <<"">>}, Req, State}. +{{/authMethods}} + +-spec content_types_accepted(Req :: cowboy_req:req(), State :: state()) -> + { + Value :: [{binary(), AcceptResource :: atom()}], + Req :: cowboy_req:req(), + State :: state() + }. + +content_types_accepted(Req, State) -> + {[ + {<<"application/json">>, handle_request_json} + ], Req, State}. + +-spec valid_content_headers(Req :: cowboy_req:req(), State :: state()) -> + {Value :: boolean(), Req :: cowboy_req:req(), State :: state()}. +{{#operations}}{{#operation}} +valid_content_headers( + Req0, + State = #state{ + operation_id = '{{operationId}}' + } +) -> + Headers = [{{#headerParams}}"{{baseName}}"{{^-last}},{{/-last}}{{/headerParams}}], + {Result, Req} = validate_headers(Headers, Req0), + {Result, Req, State}; +{{/operation}}{{/operations}} +valid_content_headers(Req, State) -> + {false, Req, State}. + +-spec content_types_provided(Req :: cowboy_req:req(), State :: state()) -> + { + Value :: [{binary(), ProvideResource :: atom()}], + Req :: cowboy_req:req(), + State :: state() + }. + +content_types_provided(Req, State) -> + {[ + {<<"application/json">>, handle_request_json} + ], Req, State}. + +-spec malformed_request(Req :: cowboy_req:req(), State :: state()) -> + {Value :: false, Req :: cowboy_req:req(), State :: state()}. + +malformed_request(Req, State) -> + {false, Req, State}. + +-spec allow_missing_post(Req :: cowboy_req:req(), State :: state()) -> + {Value :: false, Req :: cowboy_req:req(), State :: state()}. + +allow_missing_post(Req, State) -> + {false, Req, State}. + +-spec delete_resource(Req :: cowboy_req:req(), State :: state()) -> + processed_response(). + +delete_resource(Req, State) -> + handle_request_json(Req, State). + +-spec known_content_type(Req :: cowboy_req:req(), State :: state()) -> + {Value :: true, Req :: cowboy_req:req(), State :: state()}. + +known_content_type(Req, State) -> + {true, Req, State}. + +-spec valid_entity_length(Req :: cowboy_req:req(), State :: state()) -> + {Value :: true, Req :: cowboy_req:req(), State :: state()}. + +valid_entity_length(Req, State) -> + %% @TODO check the length + {true, Req, State}. + +%%%% +-type result_ok() :: { + ok, + {Status :: cowboy:http_status(), Headers :: cowboy:http_headers(), Body :: iodata()} +}. + +-type result_error() :: {error, Reason :: any()}. + +-type processed_response() :: {stop, cowboy_req:req(), state()}. + +-spec process_response(result_ok() | result_error(), cowboy_req:req(), state()) -> + processed_response(). + +process_response(Response, Req0, State = #state{operation_id = OperationID}) -> + case Response of + {ok, {Code, Headers, Body}} -> + Req = cowboy_req:reply(Code, Headers, Body, Req0), + {stop, Req, State}; + {error, Message} -> + error_logger:error_msg("Unable to process request for ~p: ~p", [OperationID, Message]), + + Req = cowboy_req:reply(400, Req0), + {stop, Req, State} + end. + +-spec handle_request_json(cowboy_req:req(), state()) -> processed_response(). + +handle_request_json( + Req0, + State = #state{ + operation_id = OperationID, + logic_handler = LogicHandler, + validator_state = ValidatorState + } +) -> + case {{packageName}}_api:populate_request(OperationID, Req0, ValidatorState) of + {ok, Populated, Req1} -> + {Code, Headers, Body} = {{packageName}}_logic_handler:handle_request( + LogicHandler, + OperationID, + Req1, + maps:merge(State#state.context, Populated) + ), + _ = {{packageName}}_api:validate_response( + OperationID, + Code, + Body, + ValidatorState + ), + PreparedBody = prepare_body(Code, Body), + Response = {ok, {Code, Headers, PreparedBody}}, + process_response(Response, Req1, State); + {error, Reason, Req1} -> + process_response({error, Reason}, Req1, State) + end. + +validate_headers(_, Req) -> {true, Req}. + +prepare_body(204, Body) when map_size(Body) == 0; length(Body) == 0 -> + <<>>; +prepare_body(304, Body) when map_size(Body) == 0; length(Body) == 0 -> + <<>>; +prepare_body(_Code, Body) -> + jsx:encode(Body). diff --git a/modules/openapi-generator/src/main/resources/erlang-server-deprecated/logic_handler.mustache b/modules/openapi-generator/src/main/resources/erlang-server-deprecated/logic_handler.mustache new file mode 100644 index 00000000000..eb0688e682c --- /dev/null +++ b/modules/openapi-generator/src/main/resources/erlang-server-deprecated/logic_handler.mustache @@ -0,0 +1,60 @@ +-module({{packageName}}_logic_handler). + +-export([handle_request/4]). +{{#authMethods}} +{{#isApiKey}} +{{#-first}} +-export([authorize_api_key/3]). +{{/-first}} +{{/isApiKey}} +{{/authMethods}} +{{^authMethods}} +-export([authorize_api_key/3]). +{{/authMethods}} +-type context() :: #{binary() => any()}. +-type handler_response() ::{ + Status :: cowboy:http_status(), + Headers :: cowboy:http_headers(), + Body :: jsx:json_term()}. + +-export_type([handler_response/0]). + +{{#authMethods}} + {{#isApiKey}} +-callback authorize_api_key( + OperationID :: {{packageName}}_api:operation_id(), + ApiKey :: binary() +) -> + Result :: boolean() | {boolean(), context()}. + {{/isApiKey}} +{{/authMethods}} + + +-callback handle_request(OperationID :: {{packageName}}_api:operation_id(), cowboy_req:req(), Context :: context()) -> + handler_response(). + +-spec handle_request( + Handler :: atom(), + OperationID :: {{packageName}}_api:operation_id(), + Request :: cowboy_req:req(), + Context :: context() +) -> + handler_response(). + +handle_request(Handler, OperationID, Req, Context) -> + Handler:handle_request(OperationID, Req, Context). + +{{#authMethods}} + {{#isApiKey}} +-spec authorize_api_key(Handler :: atom(), OperationID :: {{packageName}}_api:operation_id(), ApiKey :: binary()) -> + Result :: false | {true, context()}. +authorize_api_key(Handler, OperationID, ApiKey) -> + Handler:authorize_api_key(OperationID, ApiKey). + {{/isApiKey}} +{{/authMethods}} +{{^authMethods}} +-spec authorize_api_key(Handler :: atom(), OperationID :: {{packageName}}_api:operation_id(), ApiKey :: binary()) -> + Result :: false. +authorize_api_key(_Handler, _OperationID, _ApiKey) -> + false. +{{/authMethods}} diff --git a/modules/openapi-generator/src/main/resources/erlang-server-deprecated/openapi.mustache b/modules/openapi-generator/src/main/resources/erlang-server-deprecated/openapi.mustache new file mode 100644 index 00000000000..2c1b461cf00 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/erlang-server-deprecated/openapi.mustache @@ -0,0 +1 @@ +{{{openapi-json}}} diff --git a/modules/openapi-generator/src/main/resources/erlang-server-deprecated/rebar.config.mustache b/modules/openapi-generator/src/main/resources/erlang-server-deprecated/rebar.config.mustache new file mode 100644 index 00000000000..743b108f384 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/erlang-server-deprecated/rebar.config.mustache @@ -0,0 +1,6 @@ +{deps, [ + {cowboy, {git, "https://github.com/ninenines/cowboy.git", {tag, "2.8.0"}}}, + {rfc3339, {git, "https://github.com/talentdeficit/rfc3339.git", {tag, "master"}}}, + {jsx, {git, "https://github.com/talentdeficit/jsx.git", {tag, "v3.1.0"}}}, + {jesse, {git, "https://github.com/for-GET/jesse.git", {tag, "1.5.6"}}} +]}. diff --git a/modules/openapi-generator/src/main/resources/erlang-server-deprecated/router.mustache b/modules/openapi-generator/src/main/resources/erlang-server-deprecated/router.mustache new file mode 100644 index 00000000000..e2efc2206bc --- /dev/null +++ b/modules/openapi-generator/src/main/resources/erlang-server-deprecated/router.mustache @@ -0,0 +1,78 @@ +-module({{packageName}}_router). + +-export([get_paths/1, get_validator_state/0]). + +-type operations() :: #{ + Method :: binary() => {{packageName}}_api:operation_id() +}. + +-type init_opts() :: { + Operations :: operations(), + LogicHandler :: atom(), + ValidatorMod :: module() +}. + +-export_type([init_opts/0]). + +-spec get_paths(LogicHandler :: atom()) -> [{'_',[{ + Path :: string(), + Handler :: atom(), + InitOpts :: init_opts() +}]}]. + +get_paths(LogicHandler) -> + ValidatorState = prepare_validator(), + PreparedPaths = maps:fold( + fun(Path, #{operations := Operations, handler := Handler}, Acc) -> + [{Path, Handler, Operations} | Acc] + end, + [], + group_paths() + ), + [ + {'_', + [{P, H, {O, LogicHandler, ValidatorState}} || {P, H, O} <- PreparedPaths] + } + ]. + +group_paths() -> + maps:fold( + fun(OperationID, #{path := Path, method := Method, handler := Handler}, Acc) -> + case maps:find(Path, Acc) of + {ok, PathInfo0 = #{operations := Operations0}} -> + Operations = Operations0#{Method => OperationID}, + PathInfo = PathInfo0#{operations => Operations}, + Acc#{Path => PathInfo}; + error -> + Operations = #{Method => OperationID}, + PathInfo = #{handler => Handler, operations => Operations}, + Acc#{Path => PathInfo} + end + end, + #{}, + get_operations() + ). + +get_operations() -> + #{ {{#apiInfo}}{{#apis}}{{#operations}}{{#operation}} + '{{operationId}}' => #{ + path => "{{{basePathWithoutHost}}}{{{path}}}", + method => <<"{{httpMethod}}">>, + handler => '{{classname}}' + }{{^-last}},{{/-last}}{{/operation}}{{^-last}},{{/-last}}{{/operations}}{{/apis}}{{/apiInfo}} + }. + +get_validator_state() -> + persistent_term:get({?MODULE, validator_state}). + + +prepare_validator() -> + R = jsx:decode(element(2, file:read_file(get_openapi_path()))), + JesseState = jesse_state:new(R, [{default_schema_ver, <<"http://json-schema.org/draft-04/schema#">>}]), + persistent_term:put({?MODULE, validator_state}, JesseState), + ?MODULE. + + +get_openapi_path() -> + {ok, AppName} = application:get_application(?MODULE), + filename:join({{packageName}}_utils:priv_dir(AppName), "{{{openAPISpecName}}}.json"). diff --git a/modules/openapi-generator/src/main/resources/erlang-server-deprecated/server.mustache b/modules/openapi-generator/src/main/resources/erlang-server-deprecated/server.mustache new file mode 100644 index 00000000000..cca0af57a84 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/erlang-server-deprecated/server.mustache @@ -0,0 +1,67 @@ +-module({{packageName}}_server). + + +-define(DEFAULT_LOGIC_HANDLER, {{packageName}}_default_logic_handler). + +-export([start/2]). + +-spec start( ID :: any(), #{ + ip => inet:ip_address(), + port => inet:port_number(), + logic_handler => module(), + net_opts => [] +}) -> {ok, pid()} | {error, any()}. + +start(ID, #{ + ip := IP , + port := Port, + net_opts := NetOpts +} = Params) -> + {Transport, TransportOpts} = get_socket_transport(IP, Port, NetOpts), + LogicHandler = maps:get(logic_handler, Params, ?DEFAULT_LOGIC_HANDLER), + ExtraOpts = maps:get(cowboy_extra_opts, Params, []), + CowboyOpts = get_cowboy_config(LogicHandler, ExtraOpts), + case Transport of + ssl -> + cowboy:start_tls(ID, TransportOpts, CowboyOpts); + tcp -> + cowboy:start_clear(ID, TransportOpts, CowboyOpts) + end. + +get_socket_transport(IP, Port, Options) -> + Opts = [ + {ip, IP}, + {port, Port} + ], + case {{packageName}}_utils:get_opt(ssl, Options) of + SslOpts = [_|_] -> + {ssl, Opts ++ SslOpts}; + undefined -> + {tcp, Opts} + end. + +get_cowboy_config(LogicHandler, ExtraOpts) -> + get_cowboy_config(LogicHandler, ExtraOpts, get_default_opts(LogicHandler)). + +get_cowboy_config(_LogicHandler, [], Opts) -> + Opts; + +get_cowboy_config(LogicHandler, [{env, Env} | Rest], Opts) -> + NewEnv = case proplists:get_value(dispatch, Env) of + undefined -> [get_default_dispatch(LogicHandler) | Env]; + _ -> Env + end, + get_cowboy_config(LogicHandler, Rest, store_key(env, NewEnv, Opts)); + +get_cowboy_config(LogicHandler, [{Key, Value}| Rest], Opts) -> + get_cowboy_config(LogicHandler, Rest, store_key(Key, Value, Opts)). + +get_default_dispatch(LogicHandler) -> + Paths = {{packageName}}_router:get_paths(LogicHandler), + #{dispatch => cowboy_router:compile(Paths)}. + +get_default_opts(LogicHandler) -> + #{env => get_default_dispatch(LogicHandler)}. + +store_key(Key, Value, Opts) -> + maps:put(Key, Value, Opts). diff --git a/modules/openapi-generator/src/main/resources/erlang-server-deprecated/utils.mustache b/modules/openapi-generator/src/main/resources/erlang-server-deprecated/utils.mustache new file mode 100644 index 00000000000..b6701add7fc --- /dev/null +++ b/modules/openapi-generator/src/main/resources/erlang-server-deprecated/utils.mustache @@ -0,0 +1,173 @@ +-module({{packageName}}_utils). + +-export([to_binary/1]). +-export([to_list/1]). +-export([to_float/1]). +-export([to_int/1]). +-export([to_lower/1]). +-export([to_upper/1]). +-export([set_resp_headers/2]). +-export([to_header/1]). +-export([to_qs/1]). +-export([to_binding/1]). +-export([get_opt/2]). +-export([get_opt/3]). +-export([priv_dir/0]). +-export([priv_dir/1]). +-export([priv_path/1]). + + +-spec to_binary(iodata() | atom() | number()) -> binary(). + +to_binary(V) when is_binary(V) -> V; +to_binary(V) when is_list(V) -> iolist_to_binary(V); +to_binary(V) when is_atom(V) -> atom_to_binary(V, utf8); +to_binary(V) when is_integer(V) -> integer_to_binary(V); +to_binary(V) when is_float(V) -> float_to_binary(V). + +-spec to_list(iodata() | atom() | number()) -> string(). + +to_list(V) when is_list(V) -> V; +to_list(V) -> binary_to_list(to_binary(V)). + +-spec to_float(iodata()) -> number(). + +to_float(V) -> + Data = iolist_to_binary([V]), + case binary:split(Data, <<$.>>) of + [Data] -> + binary_to_integer(Data); + [<<>>, _] -> + binary_to_float(<<$0, Data/binary>>); + _ -> + binary_to_float(Data) + end. + +%% + +-spec to_int(integer() | binary() | list()) -> integer(). + +to_int(Data) when is_integer(Data) -> + Data; +to_int(Data) when is_binary(Data) -> + binary_to_integer(Data); +to_int(Data) when is_list(Data) -> + list_to_integer(Data). + +-spec set_resp_headers([{binary(), iodata()}], cowboy_req:req()) -> cowboy_req:req(). + +set_resp_headers([], Req) -> + Req; +set_resp_headers([{K, V} | T], Req0) -> + Req = cowboy_req:set_resp_header(K, V, Req0), + set_resp_headers(T, Req). + +-spec to_header(iodata() | atom() | number()) -> binary(). + +to_header(Name) -> + Prepared = to_binary(Name), + to_lower(Prepared). + +-spec to_qs(iodata() | atom() | number()) -> binary(). + +to_qs(Name) -> + to_binary(Name). + +-spec to_binding(iodata() | atom() | number()) -> atom(). + +to_binding(Name) -> + Prepared = to_binary(Name), + binary_to_atom(Prepared, utf8). + +-spec get_opt(any(), []) -> any(). + +get_opt(Key, Opts) -> + get_opt(Key, Opts, undefined). + +-spec get_opt(any(), [], any()) -> any(). + +get_opt(Key, Opts, Default) -> + case lists:keyfind(Key, 1, Opts) of + {_, Value} -> Value; + false -> Default + end. + +-spec priv_dir() -> file:filename(). + +priv_dir() -> + {ok, AppName} = application:get_application(), + priv_dir(AppName). + +-spec priv_dir(Application :: atom()) -> file:filename(). + +priv_dir(AppName) -> + case code:priv_dir(AppName) of + Value when is_list(Value) -> + Value ++ "/"; + _Error -> + select_priv_dir([filename:join(["apps", atom_to_list(AppName), "priv"]), "priv"]) + end. + +-spec priv_path(Relative :: file:filename()) -> file:filename(). + +priv_path(Relative) -> + filename:join(priv_dir(), Relative). + +-include_lib("kernel/include/file.hrl"). + +select_priv_dir(Paths) -> + case lists:dropwhile(fun test_priv_dir/1, Paths) of + [Path | _] -> Path; + _ -> exit(no_priv_dir) + end. + +test_priv_dir(Path) -> + case file:read_file_info(Path) of + {ok, #file_info{type = directory}} -> + false; + _ -> + true + end. + + +%% + +-spec to_lower(binary()) -> binary(). + +to_lower(S) -> + to_case(lower, S, <<>>). + +-spec to_upper(binary()) -> binary(). + +to_upper(S) -> + to_case(upper, S, <<>>). + +to_case(_Case, <<>>, Acc) -> + Acc; + +to_case(_Case, <>, _Acc) when C > 127 -> + error(badarg); + +to_case(Case = lower, <>, Acc) -> + to_case(Case, Rest, <>); + +to_case(Case = upper, <>, Acc) -> + to_case(Case, Rest, <>). + +to_lower_char(C) when is_integer(C), $A =< C, C =< $Z -> + C + 32; +to_lower_char(C) when is_integer(C), 16#C0 =< C, C =< 16#D6 -> + C + 32; +to_lower_char(C) when is_integer(C), 16#D8 =< C, C =< 16#DE -> + C + 32; +to_lower_char(C) -> + C. + +to_upper_char(C) when is_integer(C), $a =< C, C =< $z -> + C - 32; +to_upper_char(C) when is_integer(C), 16#E0 =< C, C =< 16#F6 -> + C - 32; +to_upper_char(C) when is_integer(C), 16#F8 =< C, C =< 16#FE -> + C - 32; +to_upper_char(C) -> + C.