From 29c16efd67a29b6de06a210fd1615bee385b2029 Mon Sep 17 00:00:00 2001 From: Artem Ocheredko Date: Thu, 23 Jun 2016 10:30:47 +0300 Subject: [PATCH 01/16] Initial erlang generation --- .../languages/ErlangServerCodegen.java | 262 ++++++++++++++++++ .../services/io.swagger.codegen.CodegenConfig | 3 +- .../resources/erlang-server/README.mustache | 0 .../erlang-server/api_config.mustache | 29 ++ .../resources/erlang-server/app.src.mustache | 15 + .../resources/erlang-server/handler.mustache | 97 +++++++ .../erlang-server/include/router_hrl.mustache | 11 + .../resources/erlang-server/router.mustache | 29 ++ .../resources/erlang-server/server.mustache | 45 +++ .../resources/erlang-server/swagger.mustache | 1 + .../main/resources/go-server/routers.mustache | 2 +- .../main/resources/go-server/swagger.mustache | 2 +- 12 files changed, 493 insertions(+), 3 deletions(-) create mode 100644 modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/ErlangServerCodegen.java create mode 100644 modules/swagger-codegen/src/main/resources/erlang-server/README.mustache create mode 100644 modules/swagger-codegen/src/main/resources/erlang-server/api_config.mustache create mode 100644 modules/swagger-codegen/src/main/resources/erlang-server/app.src.mustache create mode 100644 modules/swagger-codegen/src/main/resources/erlang-server/handler.mustache create mode 100644 modules/swagger-codegen/src/main/resources/erlang-server/include/router_hrl.mustache create mode 100644 modules/swagger-codegen/src/main/resources/erlang-server/router.mustache create mode 100644 modules/swagger-codegen/src/main/resources/erlang-server/server.mustache create mode 100644 modules/swagger-codegen/src/main/resources/erlang-server/swagger.mustache diff --git a/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/ErlangServerCodegen.java b/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/ErlangServerCodegen.java new file mode 100644 index 00000000000..d3acfa413d1 --- /dev/null +++ b/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/ErlangServerCodegen.java @@ -0,0 +1,262 @@ +package io.swagger.codegen.languages; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.Lists; +import com.google.common.collect.Multimap; +import io.swagger.codegen.*; +import io.swagger.models.*; +import io.swagger.util.Yaml; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.IOException; +import java.math.BigDecimal; +import java.util.*; +import java.util.Map.Entry; +import org.apache.commons.lang3.StringUtils; + +public class ErlangServerCodegen extends DefaultCodegen implements CodegenConfig { + + private static final Logger LOGGER = LoggerFactory.getLogger(ErlangServerCodegen.class); + + protected String apiVersion = "1.0.0"; + protected String apiPath = ""; + protected String packageName = "swagger"; + + public ErlangServerCodegen() { + super(); + + // set the output folder here + outputFolder = "generated-code/erlang-server"; + + if (additionalProperties.containsKey(CodegenConstants.PACKAGE_NAME)) { + setPackageName((String) additionalProperties.get(CodegenConstants.PACKAGE_NAME)); + } else { + additionalProperties.put(CodegenConstants.PACKAGE_NAME, packageName); + }; + + /** + * 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"; + + /** + * 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(); + + cliOptions.clear(); + cliOptions.add(new CliOption(CodegenConstants.PACKAGE_NAME, "Erlang package name (convention: lowercase).") + .defaultValue(this.packageName)); + /** + * 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("swagger.mustache","", "swagger.yaml")); + supportingFiles.add(new SupportingFile("app.src.mustache", "", "src" + File.separator + this.packageName + ".app.src")); + supportingFiles.add(new SupportingFile("include/router_hrl.mustache", "", toIncludeFilePath("router", "hrl"))); + supportingFiles.add(new SupportingFile("router.mustache", "", toSourceFilePath("router", "erl"))); + supportingFiles.add(new SupportingFile("server.mustache", "", toSourceFilePath("server", "erl"))); + supportingFiles.add(new SupportingFile("handler.mustache", "", toSourceFilePath("handler", "erl"))); + + /** + * Do not reload your main customizing file + */ + if(!new java.io.File(toSourceFilePath("api_config", "erl")).exists()){ + supportingFiles.add(new SupportingFile("api_config.mustache", "", toSourceFilePath("api_config", "erl"))); + } + writeOptional(outputFolder, new SupportingFile("README.mustache", "", "README.md")); + } + + @Override + public String apiPackage() { + return apiPath; + } + + /** + * Configures the type of generator. + * + * @return the CodegenType for this generator + * @see io.swagger.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 -l flag. + * + * @return the friendly name for the generator + */ + @Override + public String getName() { + return "erlang-server"; + } + + /** + * 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 using the swagger-tools project. By default, " + + "it will also generate service classes--which you can disable with the `-Dnoservice` environment variable."; + } + + @Override + public String toApiName(String name) { + if (name.length() == 0) { + return "DefaultController"; + } + return initialCaps(name); + } + + /** + * 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 reseved words + * + * @return the escaped term + */ + @Override + public String escapeReservedWord(String name) { + return "_" + name; // add an underscore to the 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) { + // camelize the model name + // phone_number => PhoneNumber + return camelize(toModelFilename(name)); + } + + @Override + public String toOperationId(String operationId) { + // method name cannot use reserved keyword, e.g. return + if (isReservedWord(operationId)) { + LOGGER.warn(operationId + " (reserved word) cannot be used as method name. Renamed to " + camelize(sanitizeName("call_" + operationId))); + operationId = "call_" + operationId; + } + + return camelize(operationId); + } + + @Override + public String toModelFilename(String name) { + if (!StringUtils.isEmpty(modelNamePrefix)) { + name = modelNamePrefix + "_" + name; + } + + if (!StringUtils.isEmpty(modelNameSuffix)) { + name = name + "_" + modelNameSuffix; + } + + name = sanitizeName(name); + + // model name cannot use reserved keyword, e.g. return + if (isReservedWord(name)) { + LOGGER.warn(name + " (reserved word) cannot be used as model name. Renamed to " + camelize("model_" + name)); + name = "model_" + name; // e.g. return => ModelReturn (after camelize) + } + + return underscore(name); + } + + @Override + public String toApiFilename(String name) { + // replace - with _ e.g. created-at => created_at + name = name.replaceAll("-", "_"); // FIXME: a parameter should not be assigned. Also declare the methods parameters as 'final'. + + return this.packageName + "_" + name + "_handler"; + } + + @Override + public Map postProcessSupportingFileData(Map objs) { + Swagger swagger = (Swagger)objs.get("swagger"); + if(swagger != null) { + try { + objs.put("swagger-yaml", Yaml.mapper().writeValueAsString(swagger)); + } catch (JsonProcessingException e) { + LOGGER.error(e.getMessage(), e); + } + } + return super.postProcessSupportingFileData(objs); + } + + protected String toSourceFilePath(String name, String extension) { + return "src" + File.separator + this.packageName + "_" + name + "." + extension; + } + + protected String toIncludeFilePath(String name, String extension) { + return "include" + File.separator + this.packageName + "_" + name + "." + extension; + } + + @Override + public Map postProcessOperations(Map objs) { + Map operations = (Map) objs.get("operations"); + List operationList = (List) operations.get("operation"); + for (CodegenOperation op : operationList) { + if (op.path != null) { + op.path = op.path.replaceAll("\\{(.*?)\\}", ":$1"); + } + } + return objs; + } + + public void setPackageName(String packageName) { + this.packageName = packageName; + } + +} diff --git a/modules/swagger-codegen/src/main/resources/META-INF/services/io.swagger.codegen.CodegenConfig b/modules/swagger-codegen/src/main/resources/META-INF/services/io.swagger.codegen.CodegenConfig index c75af63df84..54f15d1fe92 100644 --- a/modules/swagger-codegen/src/main/resources/META-INF/services/io.swagger.codegen.CodegenConfig +++ b/modules/swagger-codegen/src/main/resources/META-INF/services/io.swagger.codegen.CodegenConfig @@ -47,4 +47,5 @@ io.swagger.codegen.languages.CsharpDotNet2ClientCodegen io.swagger.codegen.languages.ClojureClientCodegen io.swagger.codegen.languages.HaskellServantCodegen io.swagger.codegen.languages.LumenServerCodegen -io.swagger.codegen.languages.GoServerCodegen \ No newline at end of file +io.swagger.codegen.languages.GoServerCodegen +io.swagger.codegen.languages.ErlangServerCodegen diff --git a/modules/swagger-codegen/src/main/resources/erlang-server/README.mustache b/modules/swagger-codegen/src/main/resources/erlang-server/README.mustache new file mode 100644 index 00000000000..e69de29bb2d diff --git a/modules/swagger-codegen/src/main/resources/erlang-server/api_config.mustache b/modules/swagger-codegen/src/main/resources/erlang-server/api_config.mustache new file mode 100644 index 00000000000..337c6e2a048 --- /dev/null +++ b/modules/swagger-codegen/src/main/resources/erlang-server/api_config.mustache @@ -0,0 +1,29 @@ +-module({{packageName}}_api_config). + +-include_lib("{{packageName}}_router.hrl"). + +-export([get_handler/1]). +-export([handle/0]). + +get_handler(OperationID) -> + case maps:find(OperationID, handlers()) of + {ok, Handler} -> + Handler; + error -> + error({unknown_operation, OperationID}) + end. + +handlers() -> + #{ + {{#apiInfo}}{{#apis}} + {{#operations}}{{#operation}} + "{{operationId}}" => default_handler(){{#hasMore}},{{/hasMore}} + {{/operation}}{{#hasMore}},{{/hasMore}}{{/operations}} + {{/apis}}{{/apiInfo}} + }. + +default_handler() -> + ?MODULE. + +handle() -> + {error, {501, [], <<"Deafault swagger response">>}}. diff --git a/modules/swagger-codegen/src/main/resources/erlang-server/app.src.mustache b/modules/swagger-codegen/src/main/resources/erlang-server/app.src.mustache new file mode 100644 index 00000000000..331e99606c1 --- /dev/null +++ b/modules/swagger-codegen/src/main/resources/erlang-server/app.src.mustache @@ -0,0 +1,15 @@ +{application, {{packageName}}, [ + {description, "Swagger rest server library"}, + {vsn, "{{apiVersion}}"}, + {registered, []}, + {applications, [ + kernel, + stdlib, + cowboy + ]}, + {env, [ + ]}, + {modules, []}, + {licenses, []}, + {links, []} +]}. diff --git a/modules/swagger-codegen/src/main/resources/erlang-server/handler.mustache b/modules/swagger-codegen/src/main/resources/erlang-server/handler.mustache new file mode 100644 index 00000000000..f776ea0edfd --- /dev/null +++ b/modules/swagger-codegen/src/main/resources/erlang-server/handler.mustache @@ -0,0 +1,97 @@ +%% basic handler +-module({{packageName}}_handler). + +-include_lib("{{packageName}}_router.hrl"). + + +-export([allowed_methods/2]). +-export([init/3]). +-export([rest_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]). +-export([handle/2]). + +-record(state, { + method, + operationID +}). + +init(_Transport, Req, [OperationID]) -> + {upgrade, protocol, cowboy_rest, Req, [OperationID]}. + +rest_init(Req0, [OperationID]) -> + {Method, Req} = cowboy_req:method(Req0), + {ok, Req, #state{method = Method, operationID = OperationID}}. + +allowed_methods(Req, State = #state{operationID = ID}) -> + Op = get_operation_by_id(ID), + {[list_to_binary(Op#operation.httpMethod)], Req, State}. + +is_authorized(Req, State) -> + {true, Req, State}. + +content_types_accepted(Req, State) -> + {[ + {<<"application/json">>, handle} + ], Req, State}. +%%get head + +valid_content_headers(Req, State) -> + {true, Req, State}. + +content_types_provided(Req, State) -> + {[ + {<<"application/json">>, handle} + ], Req, State}. + +malformed_request(Req, State) -> + {false, Req, State}. + +allow_missing_post(Req, State) -> + {false, Req, State}. + +delete_resource(Req, State) -> + {true, Req, State}. + +known_content_type(Req, State) -> + {true, Req, State}. + +valid_entity_length(Req, State) -> + {true, Req, State}. + +%%%% +handle(Req0, State = #state{operationID = ID}) -> + Handler = {{packageName}}_api_config:get_handler(ID), + case Handler:handle() of + {ok, {Headers, Body}} -> + Req1 = lists:foldl( + fun({K, V}, Req) -> + cowboy_req:set_resp_header(K, V, Req) + end, + Req0, + Headers + ), + Req = cowboy_req:set_resp_body(Body, Req1), + {true, Req, State}; + {error, {Status, Headers, Reason}} -> + {ok, Req} = cowboy_req:reply(Status, Headers, Reason, Req0), + {halt, Req, State} + end. + +get_operation_by_id(OperationID) -> + get_operation_by_id(OperationID, {{packageName}}_router:get_operations()). + +get_operation_by_id(OperationID, []) -> + error({unknown_operation, OperationID}); +get_operation_by_id(OperationID, [R = #operation{id = OperationID}| _]) -> + R; +get_operation_by_id(OperationID, [_| T]) -> + get_operation_by_id(OperationID, T). + diff --git a/modules/swagger-codegen/src/main/resources/erlang-server/include/router_hrl.mustache b/modules/swagger-codegen/src/main/resources/erlang-server/include/router_hrl.mustache new file mode 100644 index 00000000000..5414dcd581a --- /dev/null +++ b/modules/swagger-codegen/src/main/resources/erlang-server/include/router_hrl.mustache @@ -0,0 +1,11 @@ +-ifndef(__{{packageName}}_router__). +-define(__{{packageName}}_router__, 42). + +-record(operation, { + path = "", + id = "", + httpMethod = "", + baseName = "" +}). + +-endif. diff --git a/modules/swagger-codegen/src/main/resources/erlang-server/router.mustache b/modules/swagger-codegen/src/main/resources/erlang-server/router.mustache new file mode 100644 index 00000000000..0d49bf4e74f --- /dev/null +++ b/modules/swagger-codegen/src/main/resources/erlang-server/router.mustache @@ -0,0 +1,29 @@ +-module({{packageName}}_router). + +-include_lib("{{packageName}}_router.hrl"). + +-export([get_paths/0]). +-export([get_operations/0]). + +get_paths() -> + [ + {'_', [{Op#operation.path, handler(), [Op#operation.id]} || Op <- get_operations()] } + ]. + +get_operations() -> + [ + {{#apiInfo}}{{#apis}} + {{#operations}}{{#operation}} + #operation{ + path = "{{path}}", + id = "{{operationId}}", + httpMethod = "{{httpMethod}}", + baseName = "{{baseName}}" + }{{#hasMore}},{{/hasMore}} + {{/operation}}{{#hasMore}},{{/hasMore}}{{/operations}} + {{/apis}}{{/apiInfo}} + ]. + +handler() -> + list_to_atom("{{packageName}}" ++ "_handler"). + diff --git a/modules/swagger-codegen/src/main/resources/erlang-server/server.mustache b/modules/swagger-codegen/src/main/resources/erlang-server/server.mustache new file mode 100644 index 00000000000..bd89afe6430 --- /dev/null +++ b/modules/swagger-codegen/src/main/resources/erlang-server/server.mustache @@ -0,0 +1,45 @@ +-module({{packageName}}_server). + + +-define(DEFAULT_ACCEPTORS_POOLSIZE, 100). + +-export([child_spec/2]). + +child_spec(Id, #{ + ip := Ip, + port := Port, + net_opts := NetOpts +}) -> + AcceptorsPool = ?DEFAULT_ACCEPTORS_POOLSIZE, + {Transport, TransportOpts} = get_socket_transport(Ip, Port, NetOpts), + CowboyOpts = get_cowboy_config(), + ranch:child_spec({?MODULE, Id}, AcceptorsPool, + Transport, TransportOpts, cowboy_protocol, CowboyOpts). + +get_socket_transport(Ip, Port, Options) -> + Opts = [ + {ip, Ip}, + {port, Port} + ], + case get_opt(ssl, Options) of + SslOpts = [_|_] -> + {ranch_ssl, Opts ++ SslOpts}; + undefined -> + {ranch_tcp, Opts} + end. + +%%Deal with set handlers +get_cowboy_config() -> + Paths = {{packageName}}_router:get_paths(), + [{env, [{dispatch, cowboy_router:compile(Paths)}]}]. + + +%% Movie it to the utils +get_opt(Key, Opts) -> + get_opt(Key, Opts, undefined). + +get_opt(Key, Opts, Default) when is_atom(Key) -> + case lists:keyfind(Key, 1, Opts) of + {_, Value} -> Value; + false -> Default + end. diff --git a/modules/swagger-codegen/src/main/resources/erlang-server/swagger.mustache b/modules/swagger-codegen/src/main/resources/erlang-server/swagger.mustache new file mode 100644 index 00000000000..51560926bba --- /dev/null +++ b/modules/swagger-codegen/src/main/resources/erlang-server/swagger.mustache @@ -0,0 +1 @@ +{{{swagger-yaml}}} \ No newline at end of file diff --git a/modules/swagger-codegen/src/main/resources/go-server/routers.mustache b/modules/swagger-codegen/src/main/resources/go-server/routers.mustache index 5999268a528..9aa2ea1d38b 100644 --- a/modules/swagger-codegen/src/main/resources/go-server/routers.mustache +++ b/modules/swagger-codegen/src/main/resources/go-server/routers.mustache @@ -51,4 +51,4 @@ var routes = Routes{ {{operationId}}, }, {{/operation}}{{/operations}}{{/apis}}{{/apiInfo}} -} \ No newline at end of file +} diff --git a/modules/swagger-codegen/src/main/resources/go-server/swagger.mustache b/modules/swagger-codegen/src/main/resources/go-server/swagger.mustache index 51560926bba..7f569370307 100644 --- a/modules/swagger-codegen/src/main/resources/go-server/swagger.mustache +++ b/modules/swagger-codegen/src/main/resources/go-server/swagger.mustache @@ -1 +1 @@ -{{{swagger-yaml}}} \ No newline at end of file +{{{swagger-yaml}}} From 68bfbbe0439e0947b70b165c0baddb080726f9f7 Mon Sep 17 00:00:00 2001 From: Artem Ocheredko Date: Tue, 28 Jun 2016 15:29:41 +0300 Subject: [PATCH 02/16] Recfactor erlang codegen to make a minimal working example --- .../languages/ErlangServerCodegen.java | 51 +---- .../resources/erlang-server/README.mustache | 1 + .../erlang-server/api_config.mustache | 29 --- .../resources/erlang-server/app.src.mustache | 4 + .../resources/erlang-server/auth.mustache | 30 +++ .../default_logic_handler.mustache | 20 ++ .../resources/erlang-server/handler.mustache | 184 ++++++++++++++---- .../erlang-server/include/router_hrl.mustache | 11 -- .../erlang-server/logic_handler.mustache | 13 ++ .../erlang-server/rebar.config.mustache | 4 + .../erlang-server/request.hrl.mustache | 12 ++ .../resources/erlang-server/router.mustache | 33 ++-- .../resources/erlang-server/server.mustache | 28 ++- .../resources/erlang-server/swagger.mustache | 1 - .../resources/erlang-server/utils.mustache | 61 ++++++ 15 files changed, 318 insertions(+), 164 deletions(-) delete mode 100644 modules/swagger-codegen/src/main/resources/erlang-server/api_config.mustache create mode 100644 modules/swagger-codegen/src/main/resources/erlang-server/auth.mustache create mode 100644 modules/swagger-codegen/src/main/resources/erlang-server/default_logic_handler.mustache delete mode 100644 modules/swagger-codegen/src/main/resources/erlang-server/include/router_hrl.mustache create mode 100644 modules/swagger-codegen/src/main/resources/erlang-server/logic_handler.mustache create mode 100644 modules/swagger-codegen/src/main/resources/erlang-server/rebar.config.mustache create mode 100644 modules/swagger-codegen/src/main/resources/erlang-server/request.hrl.mustache delete mode 100644 modules/swagger-codegen/src/main/resources/erlang-server/swagger.mustache create mode 100644 modules/swagger-codegen/src/main/resources/erlang-server/utils.mustache diff --git a/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/ErlangServerCodegen.java b/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/ErlangServerCodegen.java index d3acfa413d1..742706a3607 100644 --- a/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/ErlangServerCodegen.java +++ b/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/ErlangServerCodegen.java @@ -92,19 +92,16 @@ public ErlangServerCodegen() { * 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("swagger.mustache","", "swagger.yaml")); + 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("include/router_hrl.mustache", "", toIncludeFilePath("router", "hrl"))); + supportingFiles.add(new SupportingFile("request.hrl.mustache", "", toSourceFilePath("request", "hrl"))); supportingFiles.add(new SupportingFile("router.mustache", "", toSourceFilePath("router", "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("handler.mustache", "", toSourceFilePath("handler", "erl"))); - - /** - * Do not reload your main customizing file - */ - if(!new java.io.File(toSourceFilePath("api_config", "erl")).exists()){ - supportingFiles.add(new SupportingFile("api_config.mustache", "", toSourceFilePath("api_config", "erl"))); - } + supportingFiles.add(new SupportingFile("default_logic_handler.mustache", "", toSourceFilePath("default_logic_handler", "erl"))); + supportingFiles.add(new SupportingFile("logic_handler.mustache", "", toSourceFilePath("logic_handler", "erl"))); writeOptional(outputFolder, new SupportingFile("README.mustache", "", "README.md")); } @@ -163,7 +160,7 @@ public String toApiName(String name) { */ @Override public String escapeReservedWord(String name) { - return "_" + name; // add an underscore to the name + return name + "_"; // add an underscore to the name } /** @@ -193,27 +190,6 @@ public String toOperationId(String operationId) { return camelize(operationId); } - @Override - public String toModelFilename(String name) { - if (!StringUtils.isEmpty(modelNamePrefix)) { - name = modelNamePrefix + "_" + name; - } - - if (!StringUtils.isEmpty(modelNameSuffix)) { - name = name + "_" + modelNameSuffix; - } - - name = sanitizeName(name); - - // model name cannot use reserved keyword, e.g. return - if (isReservedWord(name)) { - LOGGER.warn(name + " (reserved word) cannot be used as model name. Renamed to " + camelize("model_" + name)); - name = "model_" + name; // e.g. return => ModelReturn (after camelize) - } - - return underscore(name); - } - @Override public String toApiFilename(String name) { // replace - with _ e.g. created-at => created_at @@ -222,19 +198,6 @@ public String toApiFilename(String name) { return this.packageName + "_" + name + "_handler"; } - @Override - public Map postProcessSupportingFileData(Map objs) { - Swagger swagger = (Swagger)objs.get("swagger"); - if(swagger != null) { - try { - objs.put("swagger-yaml", Yaml.mapper().writeValueAsString(swagger)); - } catch (JsonProcessingException e) { - LOGGER.error(e.getMessage(), e); - } - } - return super.postProcessSupportingFileData(objs); - } - protected String toSourceFilePath(String name, String extension) { return "src" + File.separator + this.packageName + "_" + name + "." + extension; } diff --git a/modules/swagger-codegen/src/main/resources/erlang-server/README.mustache b/modules/swagger-codegen/src/main/resources/erlang-server/README.mustache index e69de29bb2d..c6276e2f810 100644 --- a/modules/swagger-codegen/src/main/resources/erlang-server/README.mustache +++ b/modules/swagger-codegen/src/main/resources/erlang-server/README.mustache @@ -0,0 +1 @@ +# Swagger rest server library for Erlang diff --git a/modules/swagger-codegen/src/main/resources/erlang-server/api_config.mustache b/modules/swagger-codegen/src/main/resources/erlang-server/api_config.mustache deleted file mode 100644 index 337c6e2a048..00000000000 --- a/modules/swagger-codegen/src/main/resources/erlang-server/api_config.mustache +++ /dev/null @@ -1,29 +0,0 @@ --module({{packageName}}_api_config). - --include_lib("{{packageName}}_router.hrl"). - --export([get_handler/1]). --export([handle/0]). - -get_handler(OperationID) -> - case maps:find(OperationID, handlers()) of - {ok, Handler} -> - Handler; - error -> - error({unknown_operation, OperationID}) - end. - -handlers() -> - #{ - {{#apiInfo}}{{#apis}} - {{#operations}}{{#operation}} - "{{operationId}}" => default_handler(){{#hasMore}},{{/hasMore}} - {{/operation}}{{#hasMore}},{{/hasMore}}{{/operations}} - {{/apis}}{{/apiInfo}} - }. - -default_handler() -> - ?MODULE. - -handle() -> - {error, {501, [], <<"Deafault swagger response">>}}. diff --git a/modules/swagger-codegen/src/main/resources/erlang-server/app.src.mustache b/modules/swagger-codegen/src/main/resources/erlang-server/app.src.mustache index 331e99606c1..10d51eb1492 100644 --- a/modules/swagger-codegen/src/main/resources/erlang-server/app.src.mustache +++ b/modules/swagger-codegen/src/main/resources/erlang-server/app.src.mustache @@ -5,6 +5,10 @@ {applications, [ kernel, stdlib, + ssl, + inets, + jsx, + jesse, cowboy ]}, {env, [ diff --git a/modules/swagger-codegen/src/main/resources/erlang-server/auth.mustache b/modules/swagger-codegen/src/main/resources/erlang-server/auth.mustache new file mode 100644 index 00000000000..f4fb70bdded --- /dev/null +++ b/modules/swagger-codegen/src/main/resources/erlang-server/auth.mustache @@ -0,0 +1,30 @@ +-module({{packageName}}_auth). + +-include("{{packageName}}_request.hrl"). + +-export([authorize_api_key/5]). + +authorize_api_key(From, KeyParam, OperationID, PR, LogicHandler) -> + ApiKey = get_api_key(From, KeyParam, PR), + case ApiKey of + undefined -> + AuthHeader = <<"">>, + {false, AuthHeader}; + _ -> + case LogicHandler:authorize_api_key(ApiKey, OperationID) of + {true, Context} -> + {true, Context}; + false -> + AuthHeader = <<"">>, + {false, AuthHeader} + end + end. + +get_api_key(header, KeyParam, #parsed_request{headers = Headers}) -> + swagger_utils:get_opt({{packageName}}_utils:string_to_header(KeyParam), Headers); + +get_api_key(qs_val, KeyParam, #parsed_request{qs_vals = QS}) -> + swagger_utils:get_opt(KeyParam, QS). + + + diff --git a/modules/swagger-codegen/src/main/resources/erlang-server/default_logic_handler.mustache b/modules/swagger-codegen/src/main/resources/erlang-server/default_logic_handler.mustache new file mode 100644 index 00000000000..0c158befe45 --- /dev/null +++ b/modules/swagger-codegen/src/main/resources/erlang-server/default_logic_handler.mustache @@ -0,0 +1,20 @@ +-module({{packageName}}_default_logic_handler). + +-behaviour({{packageName}}_logic_handler). + +-export([handle_request/2]). +{{#authMethods}} + {{#isApiKey}} +-export([authorize_api_key/2]). + {{/isApiKey}} +{{/authMethods}}{{#hasMore}};{{/hasMore}} + +{{#authMethods}} + {{#isApiKey}} +authorize_api_key(_, _) -> {true, #{}}. + {{/isApiKey}} +{{/authMethods}} + +handle_request(OperationID, Req) -> + io:format(user, "Got request to process: ~p~n", [{OperationID, Req}]), + {error, {501, [], <<"Not Implemented">>}}. diff --git a/modules/swagger-codegen/src/main/resources/erlang-server/handler.mustache b/modules/swagger-codegen/src/main/resources/erlang-server/handler.mustache index f776ea0edfd..8c1a2718ec6 100644 --- a/modules/swagger-codegen/src/main/resources/erlang-server/handler.mustache +++ b/modules/swagger-codegen/src/main/resources/erlang-server/handler.mustache @@ -1,9 +1,9 @@ %% basic handler -module({{packageName}}_handler). --include_lib("{{packageName}}_router.hrl"). - +-include("{{packageName}}_request.hrl"). +%% Cowboy REST callbacks -export([allowed_methods/2]). -export([init/3]). -export([rest_init/2]). @@ -16,39 +16,72 @@ -export([malformed_request/2]). -export([valid_content_headers/2]). -export([valid_entity_length/2]). --export([handle/2]). + +%% Handlers +-export([handle_request_json/2]). -record(state, { - method, - operationID + operation_id, + logic_handler, + validator_state, + parsed_request, + context=#{} }). -init(_Transport, Req, [OperationID]) -> - {upgrade, protocol, cowboy_rest, Req, [OperationID]}. - -rest_init(Req0, [OperationID]) -> - {Method, Req} = cowboy_req:method(Req0), - {ok, Req, #state{method = Method, operationID = OperationID}}. - -allowed_methods(Req, State = #state{operationID = ID}) -> - Op = get_operation_by_id(ID), - {[list_to_binary(Op#operation.httpMethod)], Req, State}. - -is_authorized(Req, State) -> - {true, Req, State}. +init(_Transport, Req, Opts) -> + {upgrade, protocol, cowboy_rest, Req, Opts}. + +rest_init(Req0, [OperationID, LogicHandler, ValidatorState]) -> + ParsingParams = [method, bindings, qs_vals, headers], + {PR, Req} = {{packageName}}_utils:parse_request(ParsingParams, Req0), + State = #state{ + operation_id = OperationID, + logic_handler = LogicHandler, + validator_state = ValidatorState, + parsed_request = PR + }, + {ok, Req, State}. + +{{#apiInfo}}{{#apis}} + {{#operations}}{{#operation}} + allowed_methods(Req, State = #state{operation_id = "{{operationId}}"}) -> + {[<<"{{httpMethod}}">>], Req, State}{{#hasMore}};{{/hasMore}} + {{/operation}}{{#hasMore}};{{/hasMore}}{{^hasMore}}.{{/hasMore}}{{/operations}} +{{/apis}}{{/apiInfo}} + +{{#apiInfo}}{{#apis}} + {{#operations}}{{#operation}} + is_authorized(Req, State = #state{operation_id = "{{operationId}}" = OperationID, logic_handler = LogicHandler, parsed_request = PR}) -> + {{#authMethods}} + {{#isApiKey}} + From = {{#isKeyInQuery}}qs_val{{/isKeyInQuery}}{{#isKeyInHeader}}header{{/isKeyInHeader}}, + case {{packageName}}_auth:authorize_api_key(From, "{{keyParamName}}", OperationID, PR, LogicHandler) of + {true, Context} -> {true, Req, State#state{context = Context}}; + {false, AuthHeader} -> {{false, AuthHeader}, Req, State} + end + {{/isApiKey}} + {{/authMethods}}{{#hasMore}};{{/hasMore}} + {{/operation}}{{#hasMore}};{{/hasMore}}{{^hasMore}}.{{/hasMore}}{{/operations}} +{{/apis}}{{/apiInfo}} content_types_accepted(Req, State) -> {[ - {<<"application/json">>, handle} + {<<"application/json">>, handle_request_json} ], Req, State}. %%get head -valid_content_headers(Req, State) -> - {true, Req, State}. +{{#apiInfo}}{{#apis}} + {{#operations}}{{#operation}} + valid_content_headers(Req0, State = #state{operation_id = "{{operationId}}"}) -> + Headers = [{{#headerParams}}"{{baseName}}"{{#hasMore}},{{/hasMore}}{{/headerParams}}], + {Result, Req} = validate_headers(Headers, Req0), + {Result, Req, State}{{#hasMore}};{{/hasMore}} + {{/operation}}{{#hasMore}};{{/hasMore}}{{^hasMore}}.{{/hasMore}}{{/operations}} +{{/apis}}{{/apiInfo}} content_types_provided(Req, State) -> {[ - {<<"application/json">>, handle} + {<<"application/json">>, handle_request_json} ], Req, State}. malformed_request(Req, State) -> @@ -63,21 +96,25 @@ delete_resource(Req, State) -> known_content_type(Req, State) -> {true, Req, State}. -valid_entity_length(Req, State) -> - {true, Req, State}. +valid_entity_length(Req0, State = #state{parsed_request = PR0}) -> + %% @TODO check the length + {PR, Req} = {{packageName}}_utils:parse_request(body, PR0, Req0), + {true, Req, State#state{parsed_request = PR}}. %%%% -handle(Req0, State = #state{operationID = ID}) -> - Handler = {{packageName}}_api_config:get_handler(ID), - case Handler:handle() of +handle_request_json( + Req0, + State = #state{ + operation_id = OperationID, + logic_handler = LogicHandler, + validator_state = ValidatorState, + parsed_request = PR + } +) -> + Populated = populate_model(OperationID, PR, ValidatorState), + case LogicHandler:handle_request(OperationID, Populated) of {ok, {Headers, Body}} -> - Req1 = lists:foldl( - fun({K, V}, Req) -> - cowboy_req:set_resp_header(K, V, Req) - end, - Req0, - Headers - ), + Req1 = swagger_utils:set_resp_headers(Headers, Req0), Req = cowboy_req:set_resp_body(Body, Req1), {true, Req, State}; {error, {Status, Headers, Reason}} -> @@ -85,13 +122,76 @@ handle(Req0, State = #state{operationID = ID}) -> {halt, Req, State} end. -get_operation_by_id(OperationID) -> - get_operation_by_id(OperationID, {{packageName}}_router:get_operations()). -get_operation_by_id(OperationID, []) -> - error({unknown_operation, OperationID}); -get_operation_by_id(OperationID, [R = #operation{id = OperationID}| _]) -> - R; -get_operation_by_id(OperationID, [_| T]) -> - get_operation_by_id(OperationID, T). +{{#apiInfo}}{{#apis}} + {{#operations}}{{#operation}} +populate_model("{{operationId}}", PR, ValidatorState) -> + Params = [ + {{#allParams}} + {{#isFormParam}} + { {{dataType}}, form, "{{baseName}}"} + {{/isFormParam}} + {{#isQueryParam}} + { {{dataType}}, qs_val, '{{baseName}}'} + {{/isQueryParam}} + {{#isPathParam}} + { {{dataType}}, path, '{{baseName}}'} + {{/isPathParam}} + {{#isHeaderParam}} + { {{dataType}}, header, "{{baseName}}"} + {{/isHeaderParam}} + {{#isBodyParam}} + {{#baseType}} + { model, body, "{{dataType}}"} + {{/baseType}} + {{/isBodyParam}}{{#hasMore}},{{/hasMore}} + {{/allParams}} + ], + populate_model_fields(Params, PR, ValidatorState) + {{#hasMore}};{{/hasMore}} + {{/operation}}{{#hasMore}};{{/hasMore}}{{^hasMore}}.{{/hasMore}}{{/operations}} +{{/apis}}{{/apiInfo}} + + +populate_model_fields(Params, PR, ValidatorState) -> + populate_model_fields(Params, PR, ValidatorState, #{}). + +populate_model_fields([], _, _, Model) -> + Model; + +populate_model_fields([FieldParams | T], PR, ValidatorState, Model) -> + {ok, K, V} = populate_model_field(FieldParams, PR, ValidatorState), + populate_model_fields(T, PR, ValidatorState, maps:put(K, V, Model)). + +populate_model_field({model, body, Name}, #parsed_request{body = Body}, ValidatorState) -> + PreparedBody = case Body of + <<"">> -> #{}; + _ -> jsx:decode(Body, [return_maps]) + end, + Definition = list_to_binary("#/definitions/" ++ Name), + try + jesse_schema_validator:validate_with_state( + [{<<"$ref">>, Definition}], + PreparedBody, + ValidatorState + ), + {ok, Name, PreparedBody} + catch + _:E -> + throw({invalid_body, E}) + end; + +populate_model_field({_DataType, qs_val, Name}, #parsed_request{qs_vals = QS}, _) -> + Value = {{packageName}}_utils:get_opt(Name, QS), + {ok, Name, Value}; + +populate_model_field({_DataType, header, Name}, #parsed_request{headers = Headers}, _) -> + Value = {{packageName}}_utils:get_opt(Name, Headers), + {ok, Name, Value}; + +populate_model_field({_DataType, path, Name}, #parsed_request{bindings = Bindings}, _) -> + Value = {{packageName}}_utils:get_opt(Name, Bindings), + {ok, Name, Value}. + +validate_headers(_, Req) -> {true, Req}. diff --git a/modules/swagger-codegen/src/main/resources/erlang-server/include/router_hrl.mustache b/modules/swagger-codegen/src/main/resources/erlang-server/include/router_hrl.mustache deleted file mode 100644 index 5414dcd581a..00000000000 --- a/modules/swagger-codegen/src/main/resources/erlang-server/include/router_hrl.mustache +++ /dev/null @@ -1,11 +0,0 @@ --ifndef(__{{packageName}}_router__). --define(__{{packageName}}_router__, 42). - --record(operation, { - path = "", - id = "", - httpMethod = "", - baseName = "" -}). - --endif. diff --git a/modules/swagger-codegen/src/main/resources/erlang-server/logic_handler.mustache b/modules/swagger-codegen/src/main/resources/erlang-server/logic_handler.mustache new file mode 100644 index 00000000000..3b90da860a0 --- /dev/null +++ b/modules/swagger-codegen/src/main/resources/erlang-server/logic_handler.mustache @@ -0,0 +1,13 @@ +-module({{packageName}}_logic_handler). + +{{#hasAuthMethods}} +-type context() :: #{binary() => any()}. +{{/hasAuthMethods}} +{{#authMethods}} + {{#isApiKey}} +-callback authorize_api_key(ApiKey :: binary(), OperationID :: binary()) -> Result :: boolean() | {boolean(), context()}. + {{/isApiKey}} +{{/authMethods}} + + +-callback handle_request(OperationID :: string(), Request :: any()) -> {ok, {[Header :: binary()], Result :: any()}} | {error, {Status :: integer(), [Header :: binary()], any()}}. diff --git a/modules/swagger-codegen/src/main/resources/erlang-server/rebar.config.mustache b/modules/swagger-codegen/src/main/resources/erlang-server/rebar.config.mustache new file mode 100644 index 00000000000..1c7f7d922e9 --- /dev/null +++ b/modules/swagger-codegen/src/main/resources/erlang-server/rebar.config.mustache @@ -0,0 +1,4 @@ +{deps, [ + {jsx, {git, "https://github.com/talentdeficit/jsx.git", {branch, "v2.8.0"}}}, + {jesse, {git, "https://github.com/for-GET/jesse.git", {tag, "1.4.0"}}} +]}. diff --git a/modules/swagger-codegen/src/main/resources/erlang-server/request.hrl.mustache b/modules/swagger-codegen/src/main/resources/erlang-server/request.hrl.mustache new file mode 100644 index 00000000000..b89c7e74ecf --- /dev/null +++ b/modules/swagger-codegen/src/main/resources/erlang-server/request.hrl.mustache @@ -0,0 +1,12 @@ +-ifndef({{packageName}}_request_included__). +-define({{packageName}}_request_included__, yeah). + +-record(parsed_request, { + method, + headers=[], + qs_vals=[], + body, + bindings=[] +}). + +-endif. diff --git a/modules/swagger-codegen/src/main/resources/erlang-server/router.mustache b/modules/swagger-codegen/src/main/resources/erlang-server/router.mustache index 0d49bf4e74f..b925b9947c6 100644 --- a/modules/swagger-codegen/src/main/resources/erlang-server/router.mustache +++ b/modules/swagger-codegen/src/main/resources/erlang-server/router.mustache @@ -1,29 +1,22 @@ -module({{packageName}}_router). --include_lib("{{packageName}}_router.hrl"). +-export([get_paths/2]). --export([get_paths/0]). --export([get_operations/0]). - -get_paths() -> +get_paths(LogicHandler, SwaggerPath) -> + ValidatorState = prepare_validator(SwaggerPath), [ - {'_', [{Op#operation.path, handler(), [Op#operation.id]} || Op <- get_operations()] } - ]. - -get_operations() -> - [ - {{#apiInfo}}{{#apis}} + {'_', + [ + {{#apiInfo}}{{#apis}} {{#operations}}{{#operation}} - #operation{ - path = "{{path}}", - id = "{{operationId}}", - httpMethod = "{{httpMethod}}", - baseName = "{{baseName}}" - }{{#hasMore}},{{/hasMore}} + {"{{path}}", '{{packageName}}_handler', ["{{operationId}}", LogicHandler, ValidatorState]} + {{#hasMore}},{{/hasMore}} {{/operation}}{{#hasMore}},{{/hasMore}}{{/operations}} {{/apis}}{{/apiInfo}} + ] + } ]. -handler() -> - list_to_atom("{{packageName}}" ++ "_handler"). - +prepare_validator(SwaggerPath) -> + R = jsx:decode(element(2, file:read_file(SwaggerPath))), + jesse_state:new(R, [{default_schema_ver, <<"http://json-schema.org/draft-04/schema#">>}]). diff --git a/modules/swagger-codegen/src/main/resources/erlang-server/server.mustache b/modules/swagger-codegen/src/main/resources/erlang-server/server.mustache index bd89afe6430..f09286422a8 100644 --- a/modules/swagger-codegen/src/main/resources/erlang-server/server.mustache +++ b/modules/swagger-codegen/src/main/resources/erlang-server/server.mustache @@ -2,17 +2,23 @@ -define(DEFAULT_ACCEPTORS_POOLSIZE, 100). +-define(DEFAULT_LOGIC_HANDLER, {{packageName}}_default_logic_handler). -export([child_spec/2]). child_spec(Id, #{ ip := Ip, port := Port, + swagger_path := SwaggerPath, net_opts := NetOpts -}) -> +} = Params) -> AcceptorsPool = ?DEFAULT_ACCEPTORS_POOLSIZE, {Transport, TransportOpts} = get_socket_transport(Ip, Port, NetOpts), - CowboyOpts = get_cowboy_config(), + LogicHandler = case maps:find(logic_handler, Params) of + {ok, L} -> L; + error -> ?DEFAULT_LOGIC_HANDLER + end, + CowboyOpts = get_cowboy_config(LogicHandler, SwaggerPath), ranch:child_spec({?MODULE, Id}, AcceptorsPool, Transport, TransportOpts, cowboy_protocol, CowboyOpts). @@ -21,25 +27,13 @@ get_socket_transport(Ip, Port, Options) -> {ip, Ip}, {port, Port} ], - case get_opt(ssl, Options) of + case {{packageName}}_utils:get_opt(ssl, Options) of SslOpts = [_|_] -> {ranch_ssl, Opts ++ SslOpts}; undefined -> {ranch_tcp, Opts} end. -%%Deal with set handlers -get_cowboy_config() -> - Paths = {{packageName}}_router:get_paths(), +get_cowboy_config(LogicHandler, SwaggerPath) -> + Paths = swagger_router:get_paths(LogicHandler, SwaggerPath), [{env, [{dispatch, cowboy_router:compile(Paths)}]}]. - - -%% Movie it to the utils -get_opt(Key, Opts) -> - get_opt(Key, Opts, undefined). - -get_opt(Key, Opts, Default) when is_atom(Key) -> - case lists:keyfind(Key, 1, Opts) of - {_, Value} -> Value; - false -> Default - end. diff --git a/modules/swagger-codegen/src/main/resources/erlang-server/swagger.mustache b/modules/swagger-codegen/src/main/resources/erlang-server/swagger.mustache deleted file mode 100644 index 51560926bba..00000000000 --- a/modules/swagger-codegen/src/main/resources/erlang-server/swagger.mustache +++ /dev/null @@ -1 +0,0 @@ -{{{swagger-yaml}}} \ No newline at end of file diff --git a/modules/swagger-codegen/src/main/resources/erlang-server/utils.mustache b/modules/swagger-codegen/src/main/resources/erlang-server/utils.mustache new file mode 100644 index 00000000000..16f8ff9a053 --- /dev/null +++ b/modules/swagger-codegen/src/main/resources/erlang-server/utils.mustache @@ -0,0 +1,61 @@ +-module({{packageName}}_utils). + +-include("{{packageName}}_request.hrl"). + +-export([set_resp_headers/2]). +-export([string_to_header/1]). +-export([parse_request/2]). +-export([parse_request/3]). +-export([get_opt/2]). +-export([get_opt/3]). + +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). + +string_to_header(Name) -> + list_to_binary(string:to_lower(Name)). + +parse_request(Field, Req) when is_atom(Field) -> + parse_request([Field], Req); + +parse_request(Fields, Req) -> + parse_request(Fields, #parsed_request{}, Req). + + +parse_request([], Parsed, Req) -> + {Parsed, Req}; + +parse_request(Field, Parsed, Req) when is_atom(Field) -> + parse_request([Field], Parsed, Req); + +parse_request([method | T], Parsed, Req0) -> + {V, Req} = cowboy_req:method(Req0), + parse_request(T, Parsed#parsed_request{method = V}, Req); + +parse_request([bindings | T], Parsed, Req0) -> + {V, Req} = cowboy_req:bindings(Req0), + parse_request(T, Parsed#parsed_request{bindings = V}, Req); + +parse_request([qs_vals | T], Parsed, Req0) -> + {V, Req} = cowboy_req:qs_vals(Req0), + parse_request(T, Parsed#parsed_request{qs_vals = V}, Req); + +parse_request([body | T], Parsed, Req0) -> + {ok, Body, Req} = cowboy_req:body(Req0), + parse_request(T, Parsed#parsed_request{body = Body}, Req); + +parse_request([headers | T], Parsed, Req0) -> + {V, Req} = cowboy_req:headers(Req0), + parse_request(T, Parsed#parsed_request{headers = V}, Req). + +get_opt(Key, Opts) -> + get_opt(Key, Opts, undefined). + +get_opt(Key, Opts, Default) -> + case lists:keyfind(Key, 1, Opts) of + {_, Value} -> Value; + false -> Default + end. From 0c616981fc0e1daa352585e360cc0dee70a97fac Mon Sep 17 00:00:00 2001 From: Artem Ocheredko Date: Tue, 5 Jul 2016 18:22:00 +0300 Subject: [PATCH 03/16] ft/erlang_codegen Separate handlers by resourse, add minor codegen fixes and refactoring --- .../languages/ErlangServerCodegen.java | 67 +++++++++++++------ .../resources/erlang-server/app.src.mustache | 6 +- .../default_logic_handler.mustache | 2 +- .../resources/erlang-server/handler.mustache | 44 ++++++------ .../erlang-server/logic_handler.mustache | 4 +- .../resources/erlang-server/router.mustache | 2 +- .../resources/erlang-server/server.mustache | 5 +- 7 files changed, 75 insertions(+), 55 deletions(-) diff --git a/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/ErlangServerCodegen.java b/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/ErlangServerCodegen.java index 742706a3607..fa5d293b0d7 100644 --- a/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/ErlangServerCodegen.java +++ b/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/ErlangServerCodegen.java @@ -26,7 +26,7 @@ public class ErlangServerCodegen extends DefaultCodegen implements CodegenConfig private static final Logger LOGGER = LoggerFactory.getLogger(ErlangServerCodegen.class); protected String apiVersion = "1.0.0"; - protected String apiPath = ""; + protected String apiPath = "src"; protected String packageName = "swagger"; public ErlangServerCodegen() { @@ -54,9 +54,9 @@ public ErlangServerCodegen() { * 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 + 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 @@ -76,7 +76,30 @@ public ErlangServerCodegen() { ); 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("password", "binary"); cliOptions.clear(); cliOptions.add(new CliOption(CodegenConstants.PACKAGE_NAME, "Erlang package name (convention: lowercase).") @@ -99,7 +122,7 @@ public ErlangServerCodegen() { 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("handler.mustache", "", toSourceFilePath("handler", "erl"))); + // supportingFiles.add(new SupportingFile("handler.mustache", "", toSourceFilePath("handler", "erl"))); supportingFiles.add(new SupportingFile("default_logic_handler.mustache", "", toSourceFilePath("default_logic_handler", "erl"))); supportingFiles.add(new SupportingFile("logic_handler.mustache", "", toSourceFilePath("logic_handler", "erl"))); writeOptional(outputFolder, new SupportingFile("README.mustache", "", "README.md")); @@ -147,9 +170,9 @@ public String getHelp() { @Override public String toApiName(String name) { if (name.length() == 0) { - return "DefaultController"; + return this.packageName + "_default_handler"; } - return initialCaps(name); + return this.packageName + "_" + underscore(name) + "_handler"; } /** @@ -174,8 +197,6 @@ public String apiFileFolder() { @Override public String toModelName(String name) { - // camelize the model name - // phone_number => PhoneNumber return camelize(toModelFilename(name)); } @@ -192,18 +213,7 @@ public String toOperationId(String operationId) { @Override public String toApiFilename(String name) { - // replace - with _ e.g. created-at => created_at - name = name.replaceAll("-", "_"); // FIXME: a parameter should not be assigned. Also declare the methods parameters as 'final'. - - return this.packageName + "_" + name + "_handler"; - } - - protected String toSourceFilePath(String name, String extension) { - return "src" + File.separator + this.packageName + "_" + name + "." + extension; - } - - protected String toIncludeFilePath(String name, String extension) { - return "include" + File.separator + this.packageName + "_" + name + "." + extension; + return toHandlerName(name); } @Override @@ -222,4 +232,19 @@ public void setPackageName(String packageName) { this.packageName = packageName; } + 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; + } } diff --git a/modules/swagger-codegen/src/main/resources/erlang-server/app.src.mustache b/modules/swagger-codegen/src/main/resources/erlang-server/app.src.mustache index 10d51eb1492..0bc22f45743 100644 --- a/modules/swagger-codegen/src/main/resources/erlang-server/app.src.mustache +++ b/modules/swagger-codegen/src/main/resources/erlang-server/app.src.mustache @@ -1,5 +1,5 @@ {application, {{packageName}}, [ - {description, "Swagger rest server library"}, + {description, {{#appDescription}}"{{appDescription}}"{{/appDescription}}{{^appDescription}}"Swagger rest server library"{{/appDescription}}}, {vsn, "{{apiVersion}}"}, {registered, []}, {applications, [ @@ -14,6 +14,6 @@ {env, [ ]}, {modules, []}, - {licenses, []}, - {links, []} + {licenses, [{{#licenseInfo}}"{{licenseInfo}}"{{/licenseInfo}}]}, + {links, [{{#infoUrl}}"{{infoUrl}}"{{/infoUrl}}]} ]}. diff --git a/modules/swagger-codegen/src/main/resources/erlang-server/default_logic_handler.mustache b/modules/swagger-codegen/src/main/resources/erlang-server/default_logic_handler.mustache index 0c158befe45..17276b7eac6 100644 --- a/modules/swagger-codegen/src/main/resources/erlang-server/default_logic_handler.mustache +++ b/modules/swagger-codegen/src/main/resources/erlang-server/default_logic_handler.mustache @@ -7,7 +7,7 @@ {{#isApiKey}} -export([authorize_api_key/2]). {{/isApiKey}} -{{/authMethods}}{{#hasMore}};{{/hasMore}} +{{/authMethods}} {{#authMethods}} {{#isApiKey}} diff --git a/modules/swagger-codegen/src/main/resources/erlang-server/handler.mustache b/modules/swagger-codegen/src/main/resources/erlang-server/handler.mustache index 8c1a2718ec6..5d5d77cc4a8 100644 --- a/modules/swagger-codegen/src/main/resources/erlang-server/handler.mustache +++ b/modules/swagger-codegen/src/main/resources/erlang-server/handler.mustache @@ -1,5 +1,5 @@ %% basic handler --module({{packageName}}_handler). +-module({{classname}}). -include("{{packageName}}_request.hrl"). @@ -42,27 +42,27 @@ rest_init(Req0, [OperationID, LogicHandler, ValidatorState]) -> }, {ok, Req, State}. -{{#apiInfo}}{{#apis}} {{#operations}}{{#operation}} - allowed_methods(Req, State = #state{operation_id = "{{operationId}}"}) -> - {[<<"{{httpMethod}}">>], Req, State}{{#hasMore}};{{/hasMore}} - {{/operation}}{{#hasMore}};{{/hasMore}}{{^hasMore}}.{{/hasMore}}{{/operations}} -{{/apis}}{{/apiInfo}} + allowed_methods(Req, State = #state{operation_id = '{{operationId}}'}) -> + {[<<"{{httpMethod}}">>], Req, State}; + {{/operation}}{{/operations}} + allowed_methods(Req, State) -> + {[], Req, State}. -{{#apiInfo}}{{#apis}} {{#operations}}{{#operation}} - is_authorized(Req, State = #state{operation_id = "{{operationId}}" = OperationID, logic_handler = LogicHandler, parsed_request = PR}) -> + is_authorized(Req, State = #state{operation_id = '{{operationId}}' = OperationID, logic_handler = LogicHandler, parsed_request = PR}) -> {{#authMethods}} {{#isApiKey}} From = {{#isKeyInQuery}}qs_val{{/isKeyInQuery}}{{#isKeyInHeader}}header{{/isKeyInHeader}}, case {{packageName}}_auth:authorize_api_key(From, "{{keyParamName}}", OperationID, PR, LogicHandler) of {true, Context} -> {true, Req, State#state{context = Context}}; {false, AuthHeader} -> {{false, AuthHeader}, Req, State} - end + end; {{/isApiKey}} - {{/authMethods}}{{#hasMore}};{{/hasMore}} - {{/operation}}{{#hasMore}};{{/hasMore}}{{^hasMore}}.{{/hasMore}}{{/operations}} -{{/apis}}{{/apiInfo}} + {{/authMethods}} + {{/operation}}{{/operations}} + is_authorized(Req, State) -> + {{false, <<"">>}, Req, State}. content_types_accepted(Req, State) -> {[ @@ -70,14 +70,14 @@ content_types_accepted(Req, State) -> ], Req, State}. %%get head -{{#apiInfo}}{{#apis}} {{#operations}}{{#operation}} - valid_content_headers(Req0, State = #state{operation_id = "{{operationId}}"}) -> + valid_content_headers(Req0, State = #state{operation_id = '{{operationId}}'}) -> Headers = [{{#headerParams}}"{{baseName}}"{{#hasMore}},{{/hasMore}}{{/headerParams}}], {Result, Req} = validate_headers(Headers, Req0), - {Result, Req, State}{{#hasMore}};{{/hasMore}} - {{/operation}}{{#hasMore}};{{/hasMore}}{{^hasMore}}.{{/hasMore}}{{/operations}} -{{/apis}}{{/apiInfo}} + {Result, Req, State}; + {{/operation}}{{/operations}} + valid_content_headers(Req, State) -> + {false, Req, State}. content_types_provided(Req, State) -> {[ @@ -91,7 +91,7 @@ allow_missing_post(Req, State) -> {false, Req, State}. delete_resource(Req, State) -> - {true, Req, State}. + handle_request_json(Req, State). known_content_type(Req, State) -> {true, Req, State}. @@ -123,9 +123,8 @@ handle_request_json( end. -{{#apiInfo}}{{#apis}} {{#operations}}{{#operation}} -populate_model("{{operationId}}", PR, ValidatorState) -> +populate_model('{{operationId}}', PR, ValidatorState) -> Params = [ {{#allParams}} {{#isFormParam}} @@ -150,7 +149,6 @@ populate_model("{{operationId}}", PR, ValidatorState) -> populate_model_fields(Params, PR, ValidatorState) {{#hasMore}};{{/hasMore}} {{/operation}}{{#hasMore}};{{/hasMore}}{{^hasMore}}.{{/hasMore}}{{/operations}} -{{/apis}}{{/apiInfo}} populate_model_fields(Params, PR, ValidatorState) -> @@ -165,7 +163,7 @@ populate_model_fields([FieldParams | T], PR, ValidatorState, Model) -> populate_model_field({model, body, Name}, #parsed_request{body = Body}, ValidatorState) -> PreparedBody = case Body of - <<"">> -> #{}; + <<"">> -> <<"">>; _ -> jsx:decode(Body, [return_maps]) end, Definition = list_to_binary("#/definitions/" ++ Name), @@ -178,7 +176,7 @@ populate_model_field({model, body, Name}, #parsed_request{body = Body}, Validato {ok, Name, PreparedBody} catch _:E -> - throw({invalid_body, E}) + throw({bad_request, E}) end; populate_model_field({_DataType, qs_val, Name}, #parsed_request{qs_vals = QS}, _) -> diff --git a/modules/swagger-codegen/src/main/resources/erlang-server/logic_handler.mustache b/modules/swagger-codegen/src/main/resources/erlang-server/logic_handler.mustache index 3b90da860a0..f79c7ad66ce 100644 --- a/modules/swagger-codegen/src/main/resources/erlang-server/logic_handler.mustache +++ b/modules/swagger-codegen/src/main/resources/erlang-server/logic_handler.mustache @@ -5,9 +5,9 @@ {{/hasAuthMethods}} {{#authMethods}} {{#isApiKey}} --callback authorize_api_key(ApiKey :: binary(), OperationID :: binary()) -> Result :: boolean() | {boolean(), context()}. +-callback authorize_api_key(ApiKey :: binary(), OperationID :: atom()) -> Result :: boolean() | {boolean(), context()}. {{/isApiKey}} {{/authMethods}} --callback handle_request(OperationID :: string(), Request :: any()) -> {ok, {[Header :: binary()], Result :: any()}} | {error, {Status :: integer(), [Header :: binary()], any()}}. +-callback handle_request(OperationID :: atom(), Request :: any()) -> {ok, {[Header :: binary()], Result :: any()}} | {error, {Status :: integer(), [Header :: binary()], any()}}. diff --git a/modules/swagger-codegen/src/main/resources/erlang-server/router.mustache b/modules/swagger-codegen/src/main/resources/erlang-server/router.mustache index b925b9947c6..48bfee8ce9d 100644 --- a/modules/swagger-codegen/src/main/resources/erlang-server/router.mustache +++ b/modules/swagger-codegen/src/main/resources/erlang-server/router.mustache @@ -9,7 +9,7 @@ get_paths(LogicHandler, SwaggerPath) -> [ {{#apiInfo}}{{#apis}} {{#operations}}{{#operation}} - {"{{path}}", '{{packageName}}_handler', ["{{operationId}}", LogicHandler, ValidatorState]} + {"{{path}}", '{{packageName}}_handler', ['{{operationId}}', LogicHandler, ValidatorState]} {{#hasMore}},{{/hasMore}} {{/operation}}{{#hasMore}},{{/hasMore}}{{/operations}} {{/apis}}{{/apiInfo}} diff --git a/modules/swagger-codegen/src/main/resources/erlang-server/server.mustache b/modules/swagger-codegen/src/main/resources/erlang-server/server.mustache index f09286422a8..289e4a167a3 100644 --- a/modules/swagger-codegen/src/main/resources/erlang-server/server.mustache +++ b/modules/swagger-codegen/src/main/resources/erlang-server/server.mustache @@ -14,10 +14,7 @@ child_spec(Id, #{ } = Params) -> AcceptorsPool = ?DEFAULT_ACCEPTORS_POOLSIZE, {Transport, TransportOpts} = get_socket_transport(Ip, Port, NetOpts), - LogicHandler = case maps:find(logic_handler, Params) of - {ok, L} -> L; - error -> ?DEFAULT_LOGIC_HANDLER - end, + LogicHandler = maps:get(logic_handler, Params, ?DEFAULT_LOGIC_HANDLER), CowboyOpts = get_cowboy_config(LogicHandler, SwaggerPath), ranch:child_spec({?MODULE, Id}, AcceptorsPool, Transport, TransportOpts, cowboy_protocol, CowboyOpts). From 51aeb5c17fec09aa57ae88448a9ea37d7551c1f2 Mon Sep 17 00:00:00 2001 From: Artem Ocheredko Date: Tue, 5 Jul 2016 18:43:42 +0300 Subject: [PATCH 04/16] Test commit From 430c88b52aa408596289838f54ebb1341beb77e7 Mon Sep 17 00:00:00 2001 From: Artem Ocheredko Date: Wed, 6 Jul 2016 16:42:21 +0300 Subject: [PATCH 05/16] ft/erlang_codegen Modify reouting generation --- .../resources/erlang-server/handler.mustache | 136 +++++++++--------- .../resources/erlang-server/router.mustache | 25 ++-- 2 files changed, 83 insertions(+), 78 deletions(-) diff --git a/modules/swagger-codegen/src/main/resources/erlang-server/handler.mustache b/modules/swagger-codegen/src/main/resources/erlang-server/handler.mustache index 5d5d77cc4a8..cfe4fcc77d4 100644 --- a/modules/swagger-codegen/src/main/resources/erlang-server/handler.mustache +++ b/modules/swagger-codegen/src/main/resources/erlang-server/handler.mustache @@ -31,53 +31,53 @@ init(_Transport, Req, Opts) -> {upgrade, protocol, cowboy_rest, Req, Opts}. -rest_init(Req0, [OperationID, LogicHandler, ValidatorState]) -> +rest_init(Req0, [LogicHandler, ValidatorState]) -> ParsingParams = [method, bindings, qs_vals, headers], - {PR, Req} = {{packageName}}_utils:parse_request(ParsingParams, Req0), + {PR = #parsed_request{method = Method}, Req} = {{packageName}}_utils:parse_request(ParsingParams, Req0), + OperationID = get_operation(Method), State = #state{ - operation_id = OperationID, - logic_handler = LogicHandler, - validator_state = ValidatorState, - parsed_request = PR + operation_id = OperationID, + logic_handler = LogicHandler, + validator_state = ValidatorState, + parsed_request = PR }, {ok, Req, State}. - {{#operations}}{{#operation}} - allowed_methods(Req, State = #state{operation_id = '{{operationId}}'}) -> - {[<<"{{httpMethod}}">>], Req, State}; - {{/operation}}{{/operations}} - allowed_methods(Req, State) -> - {[], Req, State}. - - {{#operations}}{{#operation}} - is_authorized(Req, State = #state{operation_id = '{{operationId}}' = OperationID, logic_handler = LogicHandler, parsed_request = PR}) -> - {{#authMethods}} - {{#isApiKey}} - From = {{#isKeyInQuery}}qs_val{{/isKeyInQuery}}{{#isKeyInHeader}}header{{/isKeyInHeader}}, - case {{packageName}}_auth:authorize_api_key(From, "{{keyParamName}}", OperationID, PR, LogicHandler) of - {true, Context} -> {true, Req, State#state{context = Context}}; - {false, AuthHeader} -> {{false, AuthHeader}, Req, State} - end; - {{/isApiKey}} - {{/authMethods}} - {{/operation}}{{/operations}} - is_authorized(Req, State) -> - {{false, <<"">>}, Req, State}. +{{#operations}}{{#operation}} +allowed_methods(Req, State = #state{operation_id = '{{operationId}}'}) -> + {[<<"{{httpMethod}}">>], Req, State}; +{{/operation}}{{/operations}} +allowed_methods(Req, State) -> + {[], Req, State}. + +{{#operations}}{{#operation}} +is_authorized(Req, State = #state{operation_id = '{{operationId}}' = OperationID, logic_handler = LogicHandler, parsed_request = PR}) -> +{{#authMethods}} + {{#isApiKey}} + From = {{#isKeyInQuery}}qs_val{{/isKeyInQuery}}{{#isKeyInHeader}}header{{/isKeyInHeader}}, + case {{packageName}}_auth:authorize_api_key(From, "{{keyParamName}}", OperationID, PR, LogicHandler) of + {true, Context} -> {true, Req, State#state{context = Context}}; + {false, AuthHeader} -> {{false, AuthHeader}, Req, State} + end; + {{/isApiKey}} +{{/authMethods}} +{{/operation}}{{/operations}} +is_authorized(Req, State) -> + {{false, <<"">>}, Req, State}. content_types_accepted(Req, State) -> {[ {<<"application/json">>, handle_request_json} ], Req, State}. -%%get head - {{#operations}}{{#operation}} - valid_content_headers(Req0, State = #state{operation_id = '{{operationId}}'}) -> - Headers = [{{#headerParams}}"{{baseName}}"{{#hasMore}},{{/hasMore}}{{/headerParams}}], - {Result, Req} = validate_headers(Headers, Req0), - {Result, Req, State}; - {{/operation}}{{/operations}} - valid_content_headers(Req, State) -> - {false, Req, State}. +{{#operations}}{{#operation}} +valid_content_headers(Req0, State = #state{operation_id = '{{operationId}}'}) -> + Headers = [{{#headerParams}}"{{baseName}}"{{#hasMore}},{{/hasMore}}{{/headerParams}}], + {Result, Req} = validate_headers(Headers, Req0), + {Result, Req, State}; +{{/operation}}{{/operations}} +valid_content_headers(Req, State) -> + {false, Req, State}. content_types_provided(Req, State) -> {[ @@ -105,10 +105,10 @@ valid_entity_length(Req0, State = #state{parsed_request = PR0}) -> handle_request_json( Req0, State = #state{ - operation_id = OperationID, - logic_handler = LogicHandler, - validator_state = ValidatorState, - parsed_request = PR + operation_id = OperationID, + logic_handler = LogicHandler, + validator_state = ValidatorState, + parsed_request = PR } ) -> Populated = populate_model(OperationID, PR, ValidatorState), @@ -127,28 +127,14 @@ handle_request_json( populate_model('{{operationId}}', PR, ValidatorState) -> Params = [ {{#allParams}} - {{#isFormParam}} - { {{dataType}}, form, "{{baseName}}"} - {{/isFormParam}} - {{#isQueryParam}} - { {{dataType}}, qs_val, '{{baseName}}'} - {{/isQueryParam}} - {{#isPathParam}} - { {{dataType}}, path, '{{baseName}}'} - {{/isPathParam}} - {{#isHeaderParam}} - { {{dataType}}, header, "{{baseName}}"} - {{/isHeaderParam}} - {{#isBodyParam}} - {{#baseType}} - { model, body, "{{dataType}}"} - {{/baseType}} - {{/isBodyParam}}{{#hasMore}},{{/hasMore}} + {{#isFormParam}}{ {{dataType}}, form, "{{baseName}}"}{{#hasMore}},{{/hasMore}}{{/isFormParam}} + {{#isQueryParam}}{ {{dataType}}, qs_val, '{{baseName}}'}{{#hasMore}},{{/hasMore}}{{/isQueryParam}} + {{#isPathParam}}{ {{dataType}}, path, '{{baseName}}'}{{#hasMore}},{{/hasMore}}{{/isPathParam}} + {{#isHeaderParam}}{ {{dataType}}, header, "{{baseName}}"}{{#hasMore}},{{/hasMore}}{{/isHeaderParam}} + {{#isBodyParam}}{{#baseType}}{ model, body, "{{dataType}}"}{{/baseType}}{{#hasMore}},{{/hasMore}}{{/isBodyParam}} {{/allParams}} ], - populate_model_fields(Params, PR, ValidatorState) - {{#hasMore}};{{/hasMore}} - {{/operation}}{{#hasMore}};{{/hasMore}}{{^hasMore}}.{{/hasMore}}{{/operations}} + populate_model_fields(Params, PR, ValidatorState){{#hasMore}};{{/hasMore}}{{/operation}}{{#hasMore}};{{/hasMore}}{{^hasMore}}.{{/hasMore}}{{/operations}} populate_model_fields(Params, PR, ValidatorState) -> @@ -163,15 +149,15 @@ populate_model_fields([FieldParams | T], PR, ValidatorState, Model) -> populate_model_field({model, body, Name}, #parsed_request{body = Body}, ValidatorState) -> PreparedBody = case Body of - <<"">> -> <<"">>; - _ -> jsx:decode(Body, [return_maps]) + <<"">> -> <<"">>; + _ -> jsx:decode(Body, [return_maps]) end, Definition = list_to_binary("#/definitions/" ++ Name), try jesse_schema_validator:validate_with_state( - [{<<"$ref">>, Definition}], - PreparedBody, - ValidatorState + [{<<"$ref">>, Definition}], + PreparedBody, + ValidatorState ), {ok, Name, PreparedBody} catch @@ -184,12 +170,24 @@ populate_model_field({_DataType, qs_val, Name}, #parsed_request{qs_vals = QS}, _ {ok, Name, Value}; populate_model_field({_DataType, header, Name}, #parsed_request{headers = Headers}, _) -> - Value = {{packageName}}_utils:get_opt(Name, Headers), - {ok, Name, Value}; + Value = {{packageName}}_utils:get_opt(Name, Headers), + {ok, Name, Value}; populate_model_field({_DataType, path, Name}, #parsed_request{bindings = Bindings}, _) -> - Value = {{packageName}}_utils:get_opt(Name, Bindings), - {ok, Name, Value}. + Value = {{packageName}}_utils:get_opt(Name, Bindings), + {ok, Name, Value}. validate_headers(_, Req) -> {true, Req}. +get_operation(Method) -> + get_operation(Method, {{packageName}}_router:get_operations()). + +get_operation(_, []) -> + undefined; + +get_operation(Method, [{OperationID, _, Method, ?MODULE} | _]) -> + OperationID; + +get_operation(Method, [R | T]) -> + get_operation(Method, T). + diff --git a/modules/swagger-codegen/src/main/resources/erlang-server/router.mustache b/modules/swagger-codegen/src/main/resources/erlang-server/router.mustache index 48bfee8ce9d..30b50904519 100644 --- a/modules/swagger-codegen/src/main/resources/erlang-server/router.mustache +++ b/modules/swagger-codegen/src/main/resources/erlang-server/router.mustache @@ -1,22 +1,29 @@ -module({{packageName}}_router). -export([get_paths/2]). +-export([get_operations/0]). get_paths(LogicHandler, SwaggerPath) -> ValidatorState = prepare_validator(SwaggerPath), + Paths = lists:usort(lists:foldl( + fun({_, Path, _, Handler}, Acc) -> + [{Path, Handler} | Acc] + end, + [], + get_operations() + )), [ {'_', - [ - {{#apiInfo}}{{#apis}} - {{#operations}}{{#operation}} - {"{{path}}", '{{packageName}}_handler', ['{{operationId}}', LogicHandler, ValidatorState]} - {{#hasMore}},{{/hasMore}} - {{/operation}}{{#hasMore}},{{/hasMore}}{{/operations}} - {{/apis}}{{/apiInfo}} - ] - } + [{P, H, [LogicHandler, ValidatorState]} || {P, H} <- Paths] + } ]. prepare_validator(SwaggerPath) -> R = jsx:decode(element(2, file:read_file(SwaggerPath))), jesse_state:new(R, [{default_schema_ver, <<"http://json-schema.org/draft-04/schema#">>}]). + + +get_operations() -> + [{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}} + {'{{operationId}}', "{{path}}", <<"{{httpMethod}}">> ,'{{classname}}'}{{#hasMore}},{{/hasMore}}{{/operation}}{{#hasMore}},{{/hasMore}}{{/operations}}{{/apis}}{{/apiInfo}} + ]. From e522011bae5e17f70d6b2f46b6d737f41a5fb03a Mon Sep 17 00:00:00 2001 From: Artem Ocheredko Date: Thu, 7 Jul 2016 01:13:32 +0300 Subject: [PATCH 06/16] ft/erlang_codegen Remove parsed request concept. Add minor refactoring and bugfixes --- .../languages/ErlangServerCodegen.java | 1 - .../resources/erlang-server/auth.mustache | 22 +-- .../resources/erlang-server/handler.mustache | 176 +++++++++--------- .../erlang-server/request.hrl.mustache | 12 -- .../resources/erlang-server/utils.mustache | 37 ---- 5 files changed, 104 insertions(+), 144 deletions(-) delete mode 100644 modules/swagger-codegen/src/main/resources/erlang-server/request.hrl.mustache diff --git a/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/ErlangServerCodegen.java b/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/ErlangServerCodegen.java index fa5d293b0d7..58d9ee9d12f 100644 --- a/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/ErlangServerCodegen.java +++ b/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/ErlangServerCodegen.java @@ -117,7 +117,6 @@ public ErlangServerCodegen() { */ 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("request.hrl.mustache", "", toSourceFilePath("request", "hrl"))); supportingFiles.add(new SupportingFile("router.mustache", "", toSourceFilePath("router", "erl"))); supportingFiles.add(new SupportingFile("server.mustache", "", toSourceFilePath("server", "erl"))); supportingFiles.add(new SupportingFile("utils.mustache", "", toSourceFilePath("utils", "erl"))); diff --git a/modules/swagger-codegen/src/main/resources/erlang-server/auth.mustache b/modules/swagger-codegen/src/main/resources/erlang-server/auth.mustache index f4fb70bdded..b6a29a9aa26 100644 --- a/modules/swagger-codegen/src/main/resources/erlang-server/auth.mustache +++ b/modules/swagger-codegen/src/main/resources/erlang-server/auth.mustache @@ -1,30 +1,30 @@ -module({{packageName}}_auth). --include("{{packageName}}_request.hrl"). - -export([authorize_api_key/5]). -authorize_api_key(From, KeyParam, OperationID, PR, LogicHandler) -> - ApiKey = get_api_key(From, KeyParam, PR), +authorize_api_key(From, KeyParam, OperationID, Req0, LogicHandler) -> + {ApiKey, Req} = get_api_key(From, KeyParam, Req0), case ApiKey of undefined -> AuthHeader = <<"">>, - {false, AuthHeader}; + {false, AuthHeader, Req}; _ -> case LogicHandler:authorize_api_key(ApiKey, OperationID) of {true, Context} -> - {true, Context}; + {true, Context, Req}; false -> AuthHeader = <<"">>, - {false, AuthHeader} + {false, AuthHeader, Req} end end. -get_api_key(header, KeyParam, #parsed_request{headers = Headers}) -> - swagger_utils:get_opt({{packageName}}_utils:string_to_header(KeyParam), Headers); +get_api_key(header, KeyParam, Req0) -> + {Headers, Req} = cowboy_req:headers(Req0), + {swagger_utils:get_opt({{packageName}}_utils:string_to_header(KeyParam), Headers), Req}; -get_api_key(qs_val, KeyParam, #parsed_request{qs_vals = QS}) -> - swagger_utils:get_opt(KeyParam, QS). +get_api_key(qs_val, KeyParam, Req0) -> + {QS, Req} = cowboy_req:qs_vals(Req0), + {swagger_utils:get_opt(KeyParam, QS), Req}. diff --git a/modules/swagger-codegen/src/main/resources/erlang-server/handler.mustache b/modules/swagger-codegen/src/main/resources/erlang-server/handler.mustache index cfe4fcc77d4..54b83f41183 100644 --- a/modules/swagger-codegen/src/main/resources/erlang-server/handler.mustache +++ b/modules/swagger-codegen/src/main/resources/erlang-server/handler.mustache @@ -1,8 +1,6 @@ %% basic handler -module({{classname}}). --include("{{packageName}}_request.hrl"). - %% Cowboy REST callbacks -export([allowed_methods/2]). -export([init/3]). @@ -24,7 +22,6 @@ operation_id, logic_handler, validator_state, - parsed_request, context=#{} }). @@ -32,14 +29,12 @@ init(_Transport, Req, Opts) -> {upgrade, protocol, cowboy_rest, Req, Opts}. rest_init(Req0, [LogicHandler, ValidatorState]) -> - ParsingParams = [method, bindings, qs_vals, headers], - {PR = #parsed_request{method = Method}, Req} = {{packageName}}_utils:parse_request(ParsingParams, Req0), + {Method, Req} = cowboy_req:method(Req0), OperationID = get_operation(Method), State = #state{ operation_id = OperationID, logic_handler = LogicHandler, - validator_state = ValidatorState, - parsed_request = PR + validator_state = ValidatorState }, {ok, Req, State}. @@ -51,13 +46,13 @@ allowed_methods(Req, State) -> {[], Req, State}. {{#operations}}{{#operation}} -is_authorized(Req, State = #state{operation_id = '{{operationId}}' = OperationID, logic_handler = LogicHandler, parsed_request = PR}) -> +is_authorized(Req0, State = #state{operation_id = '{{operationId}}' = OperationID, logic_handler = LogicHandler}) -> {{#authMethods}} {{#isApiKey}} From = {{#isKeyInQuery}}qs_val{{/isKeyInQuery}}{{#isKeyInHeader}}header{{/isKeyInHeader}}, - case {{packageName}}_auth:authorize_api_key(From, "{{keyParamName}}", OperationID, PR, LogicHandler) of - {true, Context} -> {true, Req, State#state{context = Context}}; - {false, AuthHeader} -> {{false, AuthHeader}, Req, State} + case {{packageName}}_auth:authorize_api_key(From, "{{keyParamName}}", OperationID, Req0, LogicHandler) of + {true, Context, Req} -> {true, Req, State#state{context = Context}}; + {false, AuthHeader, Req} -> {{false, AuthHeader}, Req, State} end; {{/isApiKey}} {{/authMethods}} @@ -96,91 +91,94 @@ delete_resource(Req, State) -> known_content_type(Req, State) -> {true, Req, State}. -valid_entity_length(Req0, State = #state{parsed_request = PR0}) -> +valid_entity_length(Req, State) -> %% @TODO check the length - {PR, Req} = {{packageName}}_utils:parse_request(body, PR0, Req0), - {true, Req, State#state{parsed_request = PR}}. + {true, Req, State}. %%%% handle_request_json( - Req0, - State = #state{ - operation_id = OperationID, - logic_handler = LogicHandler, - validator_state = ValidatorState, - parsed_request = PR - } + Req0, + State = #state{ + operation_id = OperationID, + logic_handler = LogicHandler, + validator_state = ValidatorState + } ) -> - Populated = populate_model(OperationID, PR, ValidatorState), - case LogicHandler:handle_request(OperationID, Populated) of - {ok, {Headers, Body}} -> - Req1 = swagger_utils:set_resp_headers(Headers, Req0), - Req = cowboy_req:set_resp_body(Body, Req1), - {true, Req, State}; - {error, {Status, Headers, Reason}} -> - {ok, Req} = cowboy_req:reply(Status, Headers, Reason, Req0), - {halt, Req, State} + case populate_model(OperationID, Req0, ValidatorState) of + {ok, Populated, Req1} -> + case LogicHandler:handle_request(OperationID, Populated) of + {ok, {Headers, Body}} -> + Req2 = swagger_utils:set_resp_headers(Headers, Req1), + Req = cowboy_req:set_resp_body(Body, Req2), + {true, Req, State}; + {error, {Status, Headers, Reason}} -> + {ok, Req} = cowboy_req:reply(Status, Headers, Reason, Req1), + {halt, Req, State} + end; + {error, _Reason, Req1} -> + {halt, Req1, State} end. - {{#operations}}{{#operation}} -populate_model('{{operationId}}', PR, ValidatorState) -> - Params = [ - {{#allParams}} - {{#isFormParam}}{ {{dataType}}, form, "{{baseName}}"}{{#hasMore}},{{/hasMore}}{{/isFormParam}} - {{#isQueryParam}}{ {{dataType}}, qs_val, '{{baseName}}'}{{#hasMore}},{{/hasMore}}{{/isQueryParam}} - {{#isPathParam}}{ {{dataType}}, path, '{{baseName}}'}{{#hasMore}},{{/hasMore}}{{/isPathParam}} - {{#isHeaderParam}}{ {{dataType}}, header, "{{baseName}}"}{{#hasMore}},{{/hasMore}}{{/isHeaderParam}} - {{#isBodyParam}}{{#baseType}}{ model, body, "{{dataType}}"}{{/baseType}}{{#hasMore}},{{/hasMore}}{{/isBodyParam}} - {{/allParams}} - ], - populate_model_fields(Params, PR, ValidatorState){{#hasMore}};{{/hasMore}}{{/operation}}{{#hasMore}};{{/hasMore}}{{^hasMore}}.{{/hasMore}}{{/operations}} - - -populate_model_fields(Params, PR, ValidatorState) -> - populate_model_fields(Params, PR, ValidatorState, #{}). - -populate_model_fields([], _, _, Model) -> - Model; - -populate_model_fields([FieldParams | T], PR, ValidatorState, Model) -> - {ok, K, V} = populate_model_field(FieldParams, PR, ValidatorState), - populate_model_fields(T, PR, ValidatorState, maps:put(K, V, Model)). - -populate_model_field({model, body, Name}, #parsed_request{body = Body}, ValidatorState) -> - PreparedBody = case Body of - <<"">> -> <<"">>; - _ -> jsx:decode(Body, [return_maps]) - end, - Definition = list_to_binary("#/definitions/" ++ Name), - try - jesse_schema_validator:validate_with_state( - [{<<"$ref">>, Definition}], - PreparedBody, - ValidatorState - ), - {ok, Name, PreparedBody} - catch - _:E -> - throw({bad_request, E}) - end; - -populate_model_field({_DataType, qs_val, Name}, #parsed_request{qs_vals = QS}, _) -> - Value = {{packageName}}_utils:get_opt(Name, QS), - {ok, Name, Value}; - -populate_model_field({_DataType, header, Name}, #parsed_request{headers = Headers}, _) -> +populate_model('{{operationId}}', Req0, ValidatorState) -> + Params = [ + {{#allParams}} + {{#isFormParam}}{ {{dataType}}, form, "{{baseName}}"}{{#hasMore}},{{/hasMore}}{{/isFormParam}} + {{#isQueryParam}}{ {{dataType}}, qs_val, '{{baseName}}'}{{#hasMore}},{{/hasMore}}{{/isQueryParam}} + {{#isPathParam}}{ {{dataType}}, path, '{{baseName}}'}{{#hasMore}},{{/hasMore}}{{/isPathParam}} + {{#isHeaderParam}}{ {{dataType}}, header, "{{baseName}}"}{{#hasMore}},{{/hasMore}}{{/isHeaderParam}} + {{#isBodyParam}}{{#baseType}}{ model, body, "{{dataType}}"}{{/baseType}}{{#hasMore}},{{/hasMore}}{{/isBodyParam}} + {{/allParams}} + ], + populate_model_fields(Params, Req0, ValidatorState){{#hasMore}};{{/hasMore}} + {{/operation}}{{#hasMore}};{{/hasMore}}{{^hasMore}}.{{/hasMore}}{{/operations}} + + +populate_model_fields(Params, Req, ValidatorState) -> + populate_model_fields(Params, Req, ValidatorState, #{}). + +populate_model_fields([], Req, _, Model) -> + {ok, Model, Req}; + +populate_model_fields([FieldParams | T], Req0, ValidatorState, Model) -> + case populate_model_field(FieldParams, Req0, ValidatorState) of + {ok, K, V, Req} -> + populate_model_fields(T, Req, ValidatorState, maps:put(K, V, Model)); + Error -> + Error + end. + +populate_model_field({model, body, Name}, Req0, ValidatorState) -> + {ok, Body, Req} = cowboy_req:body(Req0), + PreparedBody = prepare_body(Body), + Definition = list_to_binary("#/definitions/" ++ Name), + try + validate_with_schema(PreparedBody, Definition, ValidatorState), + {ok, Name, PreparedBody, Req} + catch + _:Error -> + {error, Error, Req} + end; + +populate_model_field({_DataType, qs_val, Name}, Req0, _) -> + {QS, Req} = cowboy_req:qs_vals(Req0), + Value = {{packageName}}_utils:get_opt(Name, QS), + {ok, Name, Value, Req}; + +populate_model_field({_DataType, header, Name}, Req0, _) -> + {Headers, Req} = cowboy_req:headers(Req0), Value = {{packageName}}_utils:get_opt(Name, Headers), - {ok, Name, Value}; + {ok, Name, Value, Req}; -populate_model_field({_DataType, path, Name}, #parsed_request{bindings = Bindings}, _) -> +populate_model_field({_DataType, path, Name}, Req0, _) -> + {Bindings, Req} = cowboy_req:bindings(Req0), Value = {{packageName}}_utils:get_opt(Name, Bindings), - {ok, Name, Value}. + {ok, Name, Value, Req}. validate_headers(_, Req) -> {true, Req}. get_operation(Method) -> - get_operation(Method, {{packageName}}_router:get_operations()). + get_operation(Method, {{packageName}}_router:get_operations()). get_operation(_, []) -> undefined; @@ -188,6 +186,18 @@ get_operation(_, []) -> get_operation(Method, [{OperationID, _, Method, ?MODULE} | _]) -> OperationID; -get_operation(Method, [R | T]) -> - get_operation(Method, T). +get_operation(Method, [_ | T]) -> + get_operation(Method, T). +prepare_body(Body) -> + case Body of + <<"">> -> <<"">>; + _ -> jsx:decode(Body, [return_maps]) + end. + +validate_with_schema(Body, Definition, ValidatorState) -> + jesse_schema_validator:validate_with_state( + [{<<"$ref">>, Definition}], + Body, + ValidatorState + ). diff --git a/modules/swagger-codegen/src/main/resources/erlang-server/request.hrl.mustache b/modules/swagger-codegen/src/main/resources/erlang-server/request.hrl.mustache deleted file mode 100644 index b89c7e74ecf..00000000000 --- a/modules/swagger-codegen/src/main/resources/erlang-server/request.hrl.mustache +++ /dev/null @@ -1,12 +0,0 @@ --ifndef({{packageName}}_request_included__). --define({{packageName}}_request_included__, yeah). - --record(parsed_request, { - method, - headers=[], - qs_vals=[], - body, - bindings=[] -}). - --endif. diff --git a/modules/swagger-codegen/src/main/resources/erlang-server/utils.mustache b/modules/swagger-codegen/src/main/resources/erlang-server/utils.mustache index 16f8ff9a053..f84cc2236d8 100644 --- a/modules/swagger-codegen/src/main/resources/erlang-server/utils.mustache +++ b/modules/swagger-codegen/src/main/resources/erlang-server/utils.mustache @@ -1,11 +1,7 @@ -module({{packageName}}_utils). --include("{{packageName}}_request.hrl"). - -export([set_resp_headers/2]). -export([string_to_header/1]). --export([parse_request/2]). --export([parse_request/3]). -export([get_opt/2]). -export([get_opt/3]). @@ -18,39 +14,6 @@ set_resp_headers([{K, V} | T], Req0) -> string_to_header(Name) -> list_to_binary(string:to_lower(Name)). -parse_request(Field, Req) when is_atom(Field) -> - parse_request([Field], Req); - -parse_request(Fields, Req) -> - parse_request(Fields, #parsed_request{}, Req). - - -parse_request([], Parsed, Req) -> - {Parsed, Req}; - -parse_request(Field, Parsed, Req) when is_atom(Field) -> - parse_request([Field], Parsed, Req); - -parse_request([method | T], Parsed, Req0) -> - {V, Req} = cowboy_req:method(Req0), - parse_request(T, Parsed#parsed_request{method = V}, Req); - -parse_request([bindings | T], Parsed, Req0) -> - {V, Req} = cowboy_req:bindings(Req0), - parse_request(T, Parsed#parsed_request{bindings = V}, Req); - -parse_request([qs_vals | T], Parsed, Req0) -> - {V, Req} = cowboy_req:qs_vals(Req0), - parse_request(T, Parsed#parsed_request{qs_vals = V}, Req); - -parse_request([body | T], Parsed, Req0) -> - {ok, Body, Req} = cowboy_req:body(Req0), - parse_request(T, Parsed#parsed_request{body = Body}, Req); - -parse_request([headers | T], Parsed, Req0) -> - {V, Req} = cowboy_req:headers(Req0), - parse_request(T, Parsed#parsed_request{headers = V}, Req). - get_opt(Key, Opts) -> get_opt(Key, Opts, undefined). From 060567cad913355ccd55bb1e46b0d7dde51acc0e Mon Sep 17 00:00:00 2001 From: Artem Ocheredko Date: Mon, 11 Jul 2016 10:09:21 +0000 Subject: [PATCH 07/16] ft/erlang_codegen Use swagger spec from an internal directory instead of a provided path --- .../languages/ErlangServerCodegen.java | 21 +++++++++- .../resources/erlang-server/router.mustache | 14 ++++--- .../resources/erlang-server/server.mustache | 7 ++-- .../resources/erlang-server/swagger.mustache | 1 + .../resources/erlang-server/utils.mustache | 40 +++++++++++++++++++ 5 files changed, 72 insertions(+), 11 deletions(-) create mode 100644 modules/swagger-codegen/src/main/resources/erlang-server/swagger.mustache diff --git a/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/ErlangServerCodegen.java b/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/ErlangServerCodegen.java index 58d9ee9d12f..5ab2e7cee5c 100644 --- a/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/ErlangServerCodegen.java +++ b/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/ErlangServerCodegen.java @@ -10,7 +10,7 @@ import com.google.common.collect.Multimap; import io.swagger.codegen.*; import io.swagger.models.*; -import io.swagger.util.Yaml; +import io.swagger.util.Json; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -121,7 +121,7 @@ public ErlangServerCodegen() { 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("handler.mustache", "", toSourceFilePath("handler", "erl"))); + supportingFiles.add(new SupportingFile("swagger.mustache", "", toPrivFilePath("swagger", "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"))); writeOptional(outputFolder, new SupportingFile("README.mustache", "", "README.md")); @@ -227,6 +227,19 @@ public Map postProcessOperations(Map objs) { return objs; } + @Override + public Map postProcessSupportingFileData(Map objs) { + Swagger swagger = (Swagger)objs.get("swagger"); + if(swagger != null) { + try { + objs.put("swagger-json", Json.mapper().writeValueAsString(swagger)); + } catch (JsonProcessingException e) { + LOGGER.error(e.getMessage(), e); + } + } + return super.postProcessSupportingFileData(objs); + } + public void setPackageName(String packageName) { this.packageName = packageName; } @@ -246,4 +259,8 @@ protected String toSourceFilePath(String name, String 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; + } } diff --git a/modules/swagger-codegen/src/main/resources/erlang-server/router.mustache b/modules/swagger-codegen/src/main/resources/erlang-server/router.mustache index 30b50904519..9b3e5af3c99 100644 --- a/modules/swagger-codegen/src/main/resources/erlang-server/router.mustache +++ b/modules/swagger-codegen/src/main/resources/erlang-server/router.mustache @@ -1,10 +1,10 @@ -module({{packageName}}_router). --export([get_paths/2]). +-export([get_paths/1]). -export([get_operations/0]). -get_paths(LogicHandler, SwaggerPath) -> - ValidatorState = prepare_validator(SwaggerPath), +get_paths(LogicHandler) -> + ValidatorState = prepare_validator(), Paths = lists:usort(lists:foldl( fun({_, Path, _, Handler}, Acc) -> [{Path, Handler} | Acc] @@ -18,8 +18,8 @@ get_paths(LogicHandler, SwaggerPath) -> } ]. -prepare_validator(SwaggerPath) -> - R = jsx:decode(element(2, file:read_file(SwaggerPath))), +prepare_validator() -> + R = jsx:decode(element(2, file:read_file(get_swagger_path()))), jesse_state:new(R, [{default_schema_ver, <<"http://json-schema.org/draft-04/schema#">>}]). @@ -27,3 +27,7 @@ get_operations() -> [{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}} {'{{operationId}}', "{{path}}", <<"{{httpMethod}}">> ,'{{classname}}'}{{#hasMore}},{{/hasMore}}{{/operation}}{{#hasMore}},{{/hasMore}}{{/operations}}{{/apis}}{{/apiInfo}} ]. + +get_swagger_path() -> + {ok, AppName} = application:get_application(?MODULE), + filename:join({{packageName}}_utils:priv_dir(AppName), "swagger.json"). diff --git a/modules/swagger-codegen/src/main/resources/erlang-server/server.mustache b/modules/swagger-codegen/src/main/resources/erlang-server/server.mustache index 289e4a167a3..2d2e12f04f2 100644 --- a/modules/swagger-codegen/src/main/resources/erlang-server/server.mustache +++ b/modules/swagger-codegen/src/main/resources/erlang-server/server.mustache @@ -9,13 +9,12 @@ child_spec(Id, #{ ip := Ip, port := Port, - swagger_path := SwaggerPath, net_opts := NetOpts } = Params) -> AcceptorsPool = ?DEFAULT_ACCEPTORS_POOLSIZE, {Transport, TransportOpts} = get_socket_transport(Ip, Port, NetOpts), LogicHandler = maps:get(logic_handler, Params, ?DEFAULT_LOGIC_HANDLER), - CowboyOpts = get_cowboy_config(LogicHandler, SwaggerPath), + CowboyOpts = get_cowboy_config(LogicHandler), ranch:child_spec({?MODULE, Id}, AcceptorsPool, Transport, TransportOpts, cowboy_protocol, CowboyOpts). @@ -31,6 +30,6 @@ get_socket_transport(Ip, Port, Options) -> {ranch_tcp, Opts} end. -get_cowboy_config(LogicHandler, SwaggerPath) -> - Paths = swagger_router:get_paths(LogicHandler, SwaggerPath), +get_cowboy_config(LogicHandler) -> + Paths = swagger_router:get_paths(LogicHandler), [{env, [{dispatch, cowboy_router:compile(Paths)}]}]. diff --git a/modules/swagger-codegen/src/main/resources/erlang-server/swagger.mustache b/modules/swagger-codegen/src/main/resources/erlang-server/swagger.mustache new file mode 100644 index 00000000000..9bf6bbcffba --- /dev/null +++ b/modules/swagger-codegen/src/main/resources/erlang-server/swagger.mustache @@ -0,0 +1 @@ +{{{swagger-json}}} diff --git a/modules/swagger-codegen/src/main/resources/erlang-server/utils.mustache b/modules/swagger-codegen/src/main/resources/erlang-server/utils.mustache index f84cc2236d8..5f99ea4928f 100644 --- a/modules/swagger-codegen/src/main/resources/erlang-server/utils.mustache +++ b/modules/swagger-codegen/src/main/resources/erlang-server/utils.mustache @@ -4,6 +4,9 @@ -export([string_to_header/1]). -export([get_opt/2]). -export([get_opt/3]). +-export([priv_dir/0]). +-export([priv_dir/1]). +-export([priv_path/1]). set_resp_headers([], Req) -> Req; @@ -22,3 +25,40 @@ get_opt(Key, Opts, Default) -> {_, 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. From 4aba4b6a687e113ce27ab04612304a5973932c79 Mon Sep 17 00:00:00 2001 From: Artem Ocheredko Date: Mon, 11 Jul 2016 22:49:38 +0300 Subject: [PATCH 08/16] ft/erlang_codegen Add basic response validation --- .../resources/erlang-server/handler.mustache | 45 +++++++++++++++---- .../resources/erlang-server/router.mustache | 16 ++++--- 2 files changed, 47 insertions(+), 14 deletions(-) diff --git a/modules/swagger-codegen/src/main/resources/erlang-server/handler.mustache b/modules/swagger-codegen/src/main/resources/erlang-server/handler.mustache index 54b83f41183..67643e45a3b 100644 --- a/modules/swagger-codegen/src/main/resources/erlang-server/handler.mustache +++ b/modules/swagger-codegen/src/main/resources/erlang-server/handler.mustache @@ -108,8 +108,10 @@ handle_request_json( {ok, Populated, Req1} -> case LogicHandler:handle_request(OperationID, Populated) of {ok, {Headers, Body}} -> + validate_response_body(OperationID, Body, ValidatorState), + PreparedBody = jsx:encode(Body), Req2 = swagger_utils:set_resp_headers(Headers, Req1), - Req = cowboy_req:set_resp_body(Body, Req2), + Req = cowboy_req:set_resp_body(PreparedBody, Req2), {true, Req, State}; {error, {Status, Headers, Reason}} -> {ok, Req} = cowboy_req:reply(Status, Headers, Reason, Req1), @@ -180,14 +182,17 @@ validate_headers(_, Req) -> {true, Req}. get_operation(Method) -> get_operation(Method, {{packageName}}_router:get_operations()). -get_operation(_, []) -> - undefined; - -get_operation(Method, [{OperationID, _, Method, ?MODULE} | _]) -> - OperationID; - -get_operation(Method, [_ | T]) -> - get_operation(Method, T). +get_operation(Method, Operations) -> + maps:fold( + fun + (OperationID, #{method := M, handler := ?MODULE}, _) when M =:= Method -> + OperationID; + (_, _, Acc) -> + Acc + end, + undefined, + Operations + ). prepare_body(Body) -> case Body of @@ -195,6 +200,28 @@ prepare_body(Body) -> _ -> jsx:decode(Body, [return_maps]) end. +validate_response_body(OperationID, Body, ValidatorState) -> + #{ + return_type := ReturnType, + return_base_type := ReturnBaseType + } = maps:get(OperationID, {{packageName}}_router:get_operations()), + validate_response_body(ReturnType, ReturnBaseType, Body, ValidatorState). + +validate_response_body(undefined, _, Body, _) -> + case Body of + #{} -> ok; + _ -> throw(invalid_response) + end; + +validate_response_body("list", ReturnBaseType, Body, ValidatorState) -> + [ + validate_response_body(ReturnBaseType, ReturnBaseType, Item, ValidatorState) + || Item <- Body]; + +validate_response_body(_ReturnType, ReturnBaseType, Body, ValidatorState) -> + Definition = list_to_binary("#/definitions/" ++ ReturnBaseType), + validate_with_schema(Body, Definition, ValidatorState). + validate_with_schema(Body, Definition, ValidatorState) -> jesse_schema_validator:validate_with_state( [{<<"$ref">>, Definition}], diff --git a/modules/swagger-codegen/src/main/resources/erlang-server/router.mustache b/modules/swagger-codegen/src/main/resources/erlang-server/router.mustache index 9b3e5af3c99..7dd5031d58b 100644 --- a/modules/swagger-codegen/src/main/resources/erlang-server/router.mustache +++ b/modules/swagger-codegen/src/main/resources/erlang-server/router.mustache @@ -5,8 +5,8 @@ get_paths(LogicHandler) -> ValidatorState = prepare_validator(), - Paths = lists:usort(lists:foldl( - fun({_, Path, _, Handler}, Acc) -> + Paths = lists:usort(maps:fold( + fun(_, #{path := Path, handler := Handler}, Acc) -> [{Path, Handler} | Acc] end, [], @@ -24,9 +24,15 @@ prepare_validator() -> get_operations() -> - [{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}} - {'{{operationId}}', "{{path}}", <<"{{httpMethod}}">> ,'{{classname}}'}{{#hasMore}},{{/hasMore}}{{/operation}}{{#hasMore}},{{/hasMore}}{{/operations}}{{/apis}}{{/apiInfo}} - ]. + #{ {{#apiInfo}}{{#apis}}{{#operations}}{{#operation}} + '{{operationId}}' => #{ + path => "{{path}}", + method => <<"{{httpMethod}}">>, + handler => '{{classname}}', + return_type => {{#returnType}}"{{returnType}}"{{/returnType}}{{^returnType}}undefined{{/returnType}}, + return_base_type => {{#returnBaseType}}"{{returnBaseType}}"{{/returnBaseType}}{{^returnBaseType}}undefined{{/returnBaseType}} + }{{#hasMore}},{{/hasMore}}{{/operation}}{{#hasMore}},{{/hasMore}}{{/operations}}{{/apis}}{{/apiInfo}} + }. get_swagger_path() -> {ok, AppName} = application:get_application(?MODULE), From a8713200553f796b86ee6ea4ca297bb625572275 Mon Sep 17 00:00:00 2001 From: Artem Ocheredko Date: Wed, 13 Jul 2016 16:33:03 +0300 Subject: [PATCH 09/16] ft/erlang_codegen Moved all the req validators to a separate file for test needs --- .../languages/ErlangServerCodegen.java | 1 + .../main/resources/erlang-server/api.mustache | 179 ++++++++++++++++++ .../default_logic_handler.mustache | 2 +- .../resources/erlang-server/handler.mustache | 134 ++----------- .../erlang-server/logic_handler.mustache | 2 +- .../resources/erlang-server/router.mustache | 14 +- .../resources/erlang-server/utils.mustache | 8 + 7 files changed, 203 insertions(+), 137 deletions(-) create mode 100644 modules/swagger-codegen/src/main/resources/erlang-server/api.mustache diff --git a/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/ErlangServerCodegen.java b/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/ErlangServerCodegen.java index 5ab2e7cee5c..d510b42fac2 100644 --- a/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/ErlangServerCodegen.java +++ b/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/ErlangServerCodegen.java @@ -118,6 +118,7 @@ public ErlangServerCodegen() { 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"))); diff --git a/modules/swagger-codegen/src/main/resources/erlang-server/api.mustache b/modules/swagger-codegen/src/main/resources/erlang-server/api.mustache new file mode 100644 index 00000000000..57565edddae --- /dev/null +++ b/modules/swagger-codegen/src/main/resources/erlang-server/api.mustache @@ -0,0 +1,179 @@ +-module({{packageName}}_api). + +-export([get_operations/0]). +-export([get_operation_id/2]). +-export([request_params/1]). +-export([request_param_info/2]). +-export([populate_request/3]). +-export([validate_response/4]). + +get_operations() -> + #{ {{#apiInfo}}{{#apis}}{{#operations}}{{#operation}} + '{{operationId}}' => #{ + path => "{{path}}", + method => <<"{{httpMethod}}">>, + handler => '{{classname}}' + }{{#hasMore}},{{/hasMore}}{{/operation}}{{#hasMore}},{{/hasMore}}{{/operations}}{{/apis}}{{/apiInfo}} + }. + +get_operation_id(Method, Handler) -> + get_operation_id(Method, Handler, get_operations()). + +get_operation_id(Method, Handler, Operations) -> + maps:fold( + fun + (OperationID, #{method := M, handler := H}, _) when H =:= Handler, M =:= Method -> + OperationID; + (_, _, Acc) -> + Acc + end, + undefined, + Operations + ). + +{{#apiInfo}}{{#apis}} +{{#operations}}{{#operation}} +request_params('{{operationId}}') -> + [{{#allParams}}{{^isBodyParam}} + "{{baseName}}"{{/isBodyParam}}{{#isBodyParam}} + "{{dataType}}"{{/isBodyParam}}{{#hasMore}},{{/hasMore}}{{/allParams}} + ]; +{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}} +request_params(_) -> + error(unknown_operation). + +{{#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}}, + rules => [{{#isString}} + {type, 'string'}, {{/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}}{{.}}{{/values}}{{/allowableValues}}] },{{/isEnum}}{{#required}} + {required, true}, {{/required}}{{#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}} + undefined + ] + }; +{{/allParams}}{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}} +request_param_info(OperationID, Name) -> + error({unknown_param, OperationID, Name}). + +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), + {Value, Req} = get_value(Source, Name, Req0), + [validate(R, Name, Value, ValidatorState) || R <- Rules], + {ok, Name, Value, Req}. + + +{{#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(undefined, _, Body, _) -> + case Body of + #{} -> ok; + _ -> throw(invalid_response) + end; + +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({required, true}, _Name, undefined, _ValidatorState) -> + throw(missing_param); + +validate(_, _Name, undefined, _ValidatorState) -> + ok; + +validate(schema, Name, Value, ValidatorState) -> + Definition = list_to_binary("#/definitions/" ++ Name), + try + validate_with_schema(Value, Definition, ValidatorState) + catch + _:Error -> + {error, Error} + end; + +validate(undefined, _Name, _Value, _ValidatorState) -> + ok; + +validate(Rule, Name, Value, _ValidatorState) -> + io:format("Validated ~p~n", [{Rule, Name, Value}]), + ok. + +get_value(body, _Name, Req0) -> + {ok, Body, Req} = cowboy_req:body(Req0), + Value = prepare_body(Body), + {Value, Req}; + +get_value(qs_val, Name, Req0) -> + {QS, Req} = cowboy_req:qs_vals(Req0), + Value = {{packageName}}_utils:get_opt({{packageName}}_utils:string_to_qs(Name), QS), + {Value, Req}; + +get_value(header, Name, Req0) -> + {Headers, Req} = cowboy_req:headers(Req0), + Value = {{packageName}}_utils:get_opt({{packageName}}_utils:string_to_header(Name), Headers), + {Value, Req}; + +get_value(binding, Name, Req0) -> + {Bindings, Req} = cowboy_req:bindings(Req0), + Value = {{packageName}}_utils:get_opt({{packageName}}_utils:string_to_binding(Name), Bindings), + {Value, Req}. + +prepare_body(Body) -> + case Body of + <<"">> -> <<"">>; + _ -> jsx:decode(Body, [return_maps]) + end. + +validate_with_schema(Body, Definition, ValidatorState) -> + jesse_schema_validator:validate_with_state( + [{<<"$ref">>, Definition}], + Body, + ValidatorState + ). diff --git a/modules/swagger-codegen/src/main/resources/erlang-server/default_logic_handler.mustache b/modules/swagger-codegen/src/main/resources/erlang-server/default_logic_handler.mustache index 17276b7eac6..56ce9789ff8 100644 --- a/modules/swagger-codegen/src/main/resources/erlang-server/default_logic_handler.mustache +++ b/modules/swagger-codegen/src/main/resources/erlang-server/default_logic_handler.mustache @@ -17,4 +17,4 @@ authorize_api_key(_, _) -> {true, #{}}. handle_request(OperationID, Req) -> io:format(user, "Got request to process: ~p~n", [{OperationID, Req}]), - {error, {501, [], <<"Not Implemented">>}}. + {501, [], <<"Not Implemented">>}. diff --git a/modules/swagger-codegen/src/main/resources/erlang-server/handler.mustache b/modules/swagger-codegen/src/main/resources/erlang-server/handler.mustache index 67643e45a3b..2643275a861 100644 --- a/modules/swagger-codegen/src/main/resources/erlang-server/handler.mustache +++ b/modules/swagger-codegen/src/main/resources/erlang-server/handler.mustache @@ -30,7 +30,7 @@ init(_Transport, Req, Opts) -> rest_init(Req0, [LogicHandler, ValidatorState]) -> {Method, Req} = cowboy_req:method(Req0), - OperationID = get_operation(Method), + OperationID = {{packageName}}_api:get_operation_id(Method, ?MODULE), State = #state{ operation_id = OperationID, logic_handler = LogicHandler, @@ -104,127 +104,17 @@ handle_request_json( validator_state = ValidatorState } ) -> - case populate_model(OperationID, Req0, ValidatorState) of - {ok, Populated, Req1} -> - case LogicHandler:handle_request(OperationID, Populated) of - {ok, {Headers, Body}} -> - validate_response_body(OperationID, Body, ValidatorState), - PreparedBody = jsx:encode(Body), - Req2 = swagger_utils:set_resp_headers(Headers, Req1), - Req = cowboy_req:set_resp_body(PreparedBody, Req2), - {true, Req, State}; - {error, {Status, Headers, Reason}} -> - {ok, Req} = cowboy_req:reply(Status, Headers, Reason, Req1), - {halt, Req, State} - end; - {error, _Reason, Req1} -> - {halt, Req1, State} + case {{packageName}}_api:populate_request(OperationID, Req0, ValidatorState) of + {ok, Populated, Req1} -> + case LogicHandler:handle_request(OperationID, Populated) of + {Code, Headers, Body} -> + {{packageName}}_api:validate_response(OperationID, Code, Body , ValidatorState), + PreparedBody = jsx:encode(Body), + {ok, Req} = cowboy_req:reply(Code, Headers, PreparedBody, Req1), + {halt, Req, State} + end; + {error, _Reason, Req1} -> + {halt, Req1, State} end. - {{#operations}}{{#operation}} -populate_model('{{operationId}}', Req0, ValidatorState) -> - Params = [ - {{#allParams}} - {{#isFormParam}}{ {{dataType}}, form, "{{baseName}}"}{{#hasMore}},{{/hasMore}}{{/isFormParam}} - {{#isQueryParam}}{ {{dataType}}, qs_val, '{{baseName}}'}{{#hasMore}},{{/hasMore}}{{/isQueryParam}} - {{#isPathParam}}{ {{dataType}}, path, '{{baseName}}'}{{#hasMore}},{{/hasMore}}{{/isPathParam}} - {{#isHeaderParam}}{ {{dataType}}, header, "{{baseName}}"}{{#hasMore}},{{/hasMore}}{{/isHeaderParam}} - {{#isBodyParam}}{{#baseType}}{ model, body, "{{dataType}}"}{{/baseType}}{{#hasMore}},{{/hasMore}}{{/isBodyParam}} - {{/allParams}} - ], - populate_model_fields(Params, Req0, ValidatorState){{#hasMore}};{{/hasMore}} - {{/operation}}{{#hasMore}};{{/hasMore}}{{^hasMore}}.{{/hasMore}}{{/operations}} - - -populate_model_fields(Params, Req, ValidatorState) -> - populate_model_fields(Params, Req, ValidatorState, #{}). - -populate_model_fields([], Req, _, Model) -> - {ok, Model, Req}; - -populate_model_fields([FieldParams | T], Req0, ValidatorState, Model) -> - case populate_model_field(FieldParams, Req0, ValidatorState) of - {ok, K, V, Req} -> - populate_model_fields(T, Req, ValidatorState, maps:put(K, V, Model)); - Error -> - Error - end. - -populate_model_field({model, body, Name}, Req0, ValidatorState) -> - {ok, Body, Req} = cowboy_req:body(Req0), - PreparedBody = prepare_body(Body), - Definition = list_to_binary("#/definitions/" ++ Name), - try - validate_with_schema(PreparedBody, Definition, ValidatorState), - {ok, Name, PreparedBody, Req} - catch - _:Error -> - {error, Error, Req} - end; - -populate_model_field({_DataType, qs_val, Name}, Req0, _) -> - {QS, Req} = cowboy_req:qs_vals(Req0), - Value = {{packageName}}_utils:get_opt(Name, QS), - {ok, Name, Value, Req}; - -populate_model_field({_DataType, header, Name}, Req0, _) -> - {Headers, Req} = cowboy_req:headers(Req0), - Value = {{packageName}}_utils:get_opt(Name, Headers), - {ok, Name, Value, Req}; - -populate_model_field({_DataType, path, Name}, Req0, _) -> - {Bindings, Req} = cowboy_req:bindings(Req0), - Value = {{packageName}}_utils:get_opt(Name, Bindings), - {ok, Name, Value, Req}. - validate_headers(_, Req) -> {true, Req}. - -get_operation(Method) -> - get_operation(Method, {{packageName}}_router:get_operations()). - -get_operation(Method, Operations) -> - maps:fold( - fun - (OperationID, #{method := M, handler := ?MODULE}, _) when M =:= Method -> - OperationID; - (_, _, Acc) -> - Acc - end, - undefined, - Operations - ). - -prepare_body(Body) -> - case Body of - <<"">> -> <<"">>; - _ -> jsx:decode(Body, [return_maps]) - end. - -validate_response_body(OperationID, Body, ValidatorState) -> - #{ - return_type := ReturnType, - return_base_type := ReturnBaseType - } = maps:get(OperationID, {{packageName}}_router:get_operations()), - validate_response_body(ReturnType, ReturnBaseType, Body, ValidatorState). - -validate_response_body(undefined, _, Body, _) -> - case Body of - #{} -> ok; - _ -> throw(invalid_response) - end; - -validate_response_body("list", ReturnBaseType, Body, ValidatorState) -> - [ - validate_response_body(ReturnBaseType, ReturnBaseType, Item, ValidatorState) - || Item <- Body]; - -validate_response_body(_ReturnType, ReturnBaseType, Body, ValidatorState) -> - Definition = list_to_binary("#/definitions/" ++ ReturnBaseType), - validate_with_schema(Body, Definition, ValidatorState). - -validate_with_schema(Body, Definition, ValidatorState) -> - jesse_schema_validator:validate_with_state( - [{<<"$ref">>, Definition}], - Body, - ValidatorState - ). diff --git a/modules/swagger-codegen/src/main/resources/erlang-server/logic_handler.mustache b/modules/swagger-codegen/src/main/resources/erlang-server/logic_handler.mustache index f79c7ad66ce..8ee01cd0eb5 100644 --- a/modules/swagger-codegen/src/main/resources/erlang-server/logic_handler.mustache +++ b/modules/swagger-codegen/src/main/resources/erlang-server/logic_handler.mustache @@ -10,4 +10,4 @@ {{/authMethods}} --callback handle_request(OperationID :: atom(), Request :: any()) -> {ok, {[Header :: binary()], Result :: any()}} | {error, {Status :: integer(), [Header :: binary()], any()}}. +-callback handle_request(OperationID :: atom(), Request :: any()) -> {Status :: integer(), [Header :: binary()], Body :: any()}. diff --git a/modules/swagger-codegen/src/main/resources/erlang-server/router.mustache b/modules/swagger-codegen/src/main/resources/erlang-server/router.mustache index 7dd5031d58b..12f8ab145d0 100644 --- a/modules/swagger-codegen/src/main/resources/erlang-server/router.mustache +++ b/modules/swagger-codegen/src/main/resources/erlang-server/router.mustache @@ -1,7 +1,6 @@ -module({{packageName}}_router). -export([get_paths/1]). --export([get_operations/0]). get_paths(LogicHandler) -> ValidatorState = prepare_validator(), @@ -10,7 +9,7 @@ get_paths(LogicHandler) -> [{Path, Handler} | Acc] end, [], - get_operations() + {{packageName}}_api:get_operations() )), [ {'_', @@ -23,17 +22,6 @@ prepare_validator() -> jesse_state:new(R, [{default_schema_ver, <<"http://json-schema.org/draft-04/schema#">>}]). -get_operations() -> - #{ {{#apiInfo}}{{#apis}}{{#operations}}{{#operation}} - '{{operationId}}' => #{ - path => "{{path}}", - method => <<"{{httpMethod}}">>, - handler => '{{classname}}', - return_type => {{#returnType}}"{{returnType}}"{{/returnType}}{{^returnType}}undefined{{/returnType}}, - return_base_type => {{#returnBaseType}}"{{returnBaseType}}"{{/returnBaseType}}{{^returnBaseType}}undefined{{/returnBaseType}} - }{{#hasMore}},{{/hasMore}}{{/operation}}{{#hasMore}},{{/hasMore}}{{/operations}}{{/apis}}{{/apiInfo}} - }. - get_swagger_path() -> {ok, AppName} = application:get_application(?MODULE), filename:join({{packageName}}_utils:priv_dir(AppName), "swagger.json"). diff --git a/modules/swagger-codegen/src/main/resources/erlang-server/utils.mustache b/modules/swagger-codegen/src/main/resources/erlang-server/utils.mustache index 5f99ea4928f..9b1b224bb2d 100644 --- a/modules/swagger-codegen/src/main/resources/erlang-server/utils.mustache +++ b/modules/swagger-codegen/src/main/resources/erlang-server/utils.mustache @@ -2,6 +2,8 @@ -export([set_resp_headers/2]). -export([string_to_header/1]). +-export([string_to_qs/1]). +-export([string_to_binding/1]). -export([get_opt/2]). -export([get_opt/3]). -export([priv_dir/0]). @@ -17,6 +19,12 @@ set_resp_headers([{K, V} | T], Req0) -> string_to_header(Name) -> list_to_binary(string:to_lower(Name)). +string_to_qs(Name) -> + list_to_atom(string:to_lower(Name)). + +string_to_binding(Name) -> + list_to_atom(string:to_lower(Name)). + get_opt(Key, Opts) -> get_opt(Key, Opts, undefined). From 3156c58df2bf3a37699a053dd36c5f3e036d573b Mon Sep 17 00:00:00 2001 From: Artem Ocheredko Date: Thu, 14 Jul 2016 04:03:09 +0300 Subject: [PATCH 10/16] ft/erlang_codegen Add basic param validation --- .../main/resources/erlang-server/api.mustache | 174 ++++++++++++++---- .../resources/erlang-server/handler.mustache | 5 +- .../resources/erlang-server/router.mustache | 31 +++- .../resources/erlang-server/utils.mustache | 43 +++++ 4 files changed, 212 insertions(+), 41 deletions(-) diff --git a/modules/swagger-codegen/src/main/resources/erlang-server/api.mustache b/modules/swagger-codegen/src/main/resources/erlang-server/api.mustache index 57565edddae..0ceb6a41dbb 100644 --- a/modules/swagger-codegen/src/main/resources/erlang-server/api.mustache +++ b/modules/swagger-codegen/src/main/resources/erlang-server/api.mustache @@ -1,35 +1,10 @@ -module({{packageName}}_api). --export([get_operations/0]). --export([get_operation_id/2]). -export([request_params/1]). -export([request_param_info/2]). -export([populate_request/3]). -export([validate_response/4]). -get_operations() -> - #{ {{#apiInfo}}{{#apis}}{{#operations}}{{#operation}} - '{{operationId}}' => #{ - path => "{{path}}", - method => <<"{{httpMethod}}">>, - handler => '{{classname}}' - }{{#hasMore}},{{/hasMore}}{{/operation}}{{#hasMore}},{{/hasMore}}{{/operations}}{{/apis}}{{/apiInfo}} - }. - -get_operation_id(Method, Handler) -> - get_operation_id(Method, Handler, get_operations()). - -get_operation_id(Method, Handler, Operations) -> - maps:fold( - fun - (OperationID, #{method := M, handler := H}, _) when H =:= Handler, M =:= Method -> - OperationID; - (_, _, Acc) -> - Acc - end, - undefined, - Operations - ). {{#apiInfo}}{{#apis}} {{#operations}}{{#operation}} @@ -48,7 +23,7 @@ request_param_info('{{operationId}}', {{^isBodyParam}}"{{baseName}}"{{/isBodyPar #{ source => {{#isQueryParam}}qs_val{{/isQueryParam}} {{#isPathParam}}binding{{/isPathParam}} {{#isHeaderParam}}header{{/isHeaderParam}}{{#isBodyParam}}body{{/isBodyParam}}, rules => [{{#isString}} - {type, 'string'}, {{/isString}}{{#isInteger}} + {type, 'binary'}, {{/isString}}{{#isInteger}} {type, 'integer'}, {{/isInteger}}{{#isLong}} {type, 'integer'}, {{/isLong}}{{#isFloat}} {type, 'float'}, {{/isFloat}}{{#isDouble}} @@ -59,7 +34,7 @@ request_param_info('{{operationId}}', {{^isBodyParam}}"{{baseName}}"{{/isBodyPar {type, 'date'}, {{/isDate}}{{#isDateTime}} {type, 'datetime'}, {{/isDateTime}}{{#isEnum}} {enum, [{{#allowableValues}}{{#values}}{{.}}{{/values}}{{/allowableValues}}] },{{/isEnum}}{{#required}} - {required, true}, {{/required}}{{#maximum}} + required, {{/required}}{{#maximum}} {max, {{maximum}} }, {{/maximum}}{{#exclusiveMaximum}} {exclusive_max, {{exclusiveMaximum}} }, {{/exclusiveMaximum}}{{#minimum}} {min, {{minimum}} }, {{/minimum}}{{#exclusiveMinimum}} @@ -68,7 +43,7 @@ request_param_info('{{operationId}}', {{^isBodyParam}}"{{baseName}}"{{/isBodyPar {min_length, {{minLength}} }, {{/minLength}}{{#pattern}} {pattern, "{{pattern}}" }, {{/pattern}}{{#isBodyParam}} schema, {{/isBodyParam}} - undefined + ok ] }; {{/allParams}}{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}} @@ -93,8 +68,11 @@ populate_request_params(OperationID, [FieldParams | T], Req0, ValidatorState, Mo populate_request_param(OperationID, Name, Req0, ValidatorState) -> #{rules := Rules, source := Source} = request_param_info(OperationID, Name), {Value, Req} = get_value(Source, Name, Req0), - [validate(R, Name, Value, ValidatorState) || R <- Rules], - {ok, Name, Value, Req}. + case prepare_param(Rules, Name, Value, ValidatorState) of + {ok, Result} -> {ok, Name, Result, Req}; + {error, Reason} -> + {error, Reason, Req} + end. {{#apiInfo}}{{#apis}} @@ -123,28 +101,127 @@ validate_response_body(_, ReturnBaseType, Body, ValidatorState) -> validate(schema, ReturnBaseType, Body, ValidatorState). %%% -validate({required, true}, _Name, undefined, _ValidatorState) -> - throw(missing_param); +validate( Rule = required, Name, Value, _ValidatorState) -> + case Value of + undefined -> validation_error(Rule, Name); + _ -> ok + end; -validate(_, _Name, undefined, _ValidatorState) -> +validate( _, _Name, undefined, _ValidatorState) -> ok; -validate(schema, Name, Value, ValidatorState) -> +validate( Rule = {type, 'integer'}, Name, Value, _ValidatorState) -> + try + {ok, {{packageName}}_utils:to_int(Value)} + catch + _:_ -> + validation_error(Rule, Name) + end; + +validate( Rule = {type, 'float'}, Name, Value, _ValidatorState) -> + try + {ok, {{packageName}}_utils:to_float(Value)} + catch + _:_ -> + 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), + case binary_to_existing_atom(V, utf8) of + B when is_boolean(B) -> {ok, B}; + _ -> 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) -> + case lists:member(Value, Values) of + true -> ok; + false -> 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("#/definitions/" ++ Name), try validate_with_schema(Value, Definition, ValidatorState) catch - _:Error -> - {error, Error} + _:_ -> + validation_error(Rule, Name) end; -validate(undefined, _Name, _Value, _ValidatorState) -> +validate(ok, _Name, _Value, _ValidatorState) -> ok; validate(Rule, Name, Value, _ValidatorState) -> - io:format("Validated ~p~n", [{Rule, Name, Value}]), + io:format("Skipping unknown validation: ~p~n", [{Rule, Name, Value}]), ok. +validation_error(Name, Violated) -> + throw({wrong_param, Name, Violated}). + get_value(body, _Name, Req0) -> {ok, Body, Req} = cowboy_req:body(Req0), Value = prepare_body(Body), @@ -177,3 +254,24 @@ validate_with_schema(Body, Definition, ValidatorState) -> 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/swagger-codegen/src/main/resources/erlang-server/handler.mustache b/modules/swagger-codegen/src/main/resources/erlang-server/handler.mustache index 2643275a861..ff32cedc81b 100644 --- a/modules/swagger-codegen/src/main/resources/erlang-server/handler.mustache +++ b/modules/swagger-codegen/src/main/resources/erlang-server/handler.mustache @@ -30,7 +30,7 @@ init(_Transport, Req, Opts) -> rest_init(Req0, [LogicHandler, ValidatorState]) -> {Method, Req} = cowboy_req:method(Req0), - OperationID = {{packageName}}_api:get_operation_id(Method, ?MODULE), + OperationID = {{packageName}}_router:get_operation_id(Method, ?MODULE), State = #state{ operation_id = OperationID, logic_handler = LogicHandler, @@ -114,7 +114,8 @@ handle_request_json( {halt, Req, State} end; {error, _Reason, Req1} -> - {halt, Req1, State} + {ok, Req} = cowboy_req:reply(400, [], <<"">>, Req1), + {halt, Req, State} end. validate_headers(_, Req) -> {true, Req}. diff --git a/modules/swagger-codegen/src/main/resources/erlang-server/router.mustache b/modules/swagger-codegen/src/main/resources/erlang-server/router.mustache index 12f8ab145d0..03a92431acc 100644 --- a/modules/swagger-codegen/src/main/resources/erlang-server/router.mustache +++ b/modules/swagger-codegen/src/main/resources/erlang-server/router.mustache @@ -1,6 +1,8 @@ -module({{packageName}}_router). -export([get_paths/1]). +-export([get_operations/0]). +-export([get_operation_id/2]). get_paths(LogicHandler) -> ValidatorState = prepare_validator(), @@ -9,7 +11,7 @@ get_paths(LogicHandler) -> [{Path, Handler} | Acc] end, [], - {{packageName}}_api:get_operations() + get_operations() )), [ {'_', @@ -17,6 +19,31 @@ get_paths(LogicHandler) -> } ]. + +get_operations() -> + #{ {{#apiInfo}}{{#apis}}{{#operations}}{{#operation}} + '{{operationId}}' => #{ + path => "{{path}}", + method => <<"{{httpMethod}}">>, + handler => '{{classname}}' + }{{#hasMore}},{{/hasMore}}{{/operation}}{{#hasMore}},{{/hasMore}}{{/operations}}{{/apis}}{{/apiInfo}} + }. + +get_operation_id(Method, Handler) -> + get_operation_id(Method, Handler, get_operations()). + +get_operation_id(Method, Handler, Operations) -> + maps:fold( + fun + (OperationID, #{method := M, handler := H}, _) when H =:= Handler, M =:= Method -> + OperationID; + (_, _, Acc) -> + Acc + end, + undefined, + Operations + ). + prepare_validator() -> R = jsx:decode(element(2, file:read_file(get_swagger_path()))), jesse_state:new(R, [{default_schema_ver, <<"http://json-schema.org/draft-04/schema#">>}]). @@ -25,3 +52,5 @@ prepare_validator() -> get_swagger_path() -> {ok, AppName} = application:get_application(?MODULE), filename:join({{packageName}}_utils:priv_dir(AppName), "swagger.json"). + + diff --git a/modules/swagger-codegen/src/main/resources/erlang-server/utils.mustache b/modules/swagger-codegen/src/main/resources/erlang-server/utils.mustache index 9b1b224bb2d..6a44dec9e1e 100644 --- a/modules/swagger-codegen/src/main/resources/erlang-server/utils.mustache +++ b/modules/swagger-codegen/src/main/resources/erlang-server/utils.mustache @@ -1,5 +1,9 @@ -module({{packageName}}_utils). +-export([to_binary/1]). +-export([to_list/1]). +-export([to_float/1]). +-export([to_int/1]). -export([set_resp_headers/2]). -export([string_to_header/1]). -export([string_to_qs/1]). @@ -10,6 +14,45 @@ -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). + + set_resp_headers([], Req) -> Req; set_resp_headers([{K, V} | T], Req0) -> From c7fa6cbb47571d9449f5a42fab8af99e07c3830e Mon Sep 17 00:00:00 2001 From: Artem Ocheredko Date: Tue, 19 Jul 2016 17:45:48 +0300 Subject: [PATCH 11/16] Add refactoring: OperationIDs are atoms now Fix schema validation Add todo list --- .../resources/erlang-server/README.mustache | 11 ++++ .../main/resources/erlang-server/api.mustache | 35 +++++----- .../resources/erlang-server/auth.mustache | 2 +- .../resources/erlang-server/handler.mustache | 3 +- .../resources/erlang-server/utils.mustache | 65 ++++++++++++++++--- 5 files changed, 87 insertions(+), 29 deletions(-) diff --git a/modules/swagger-codegen/src/main/resources/erlang-server/README.mustache b/modules/swagger-codegen/src/main/resources/erlang-server/README.mustache index c6276e2f810..d9858d4cc03 100644 --- a/modules/swagger-codegen/src/main/resources/erlang-server/README.mustache +++ b/modules/swagger-codegen/src/main/resources/erlang-server/README.mustache @@ -1 +1,12 @@ # Swagger rest server library for Erlang + +## TODO +- [ ] Write specs for static exported fuctions +- [ ] Write specs for generated exported fuctions +- [ ] Add datetime/date validation +- [ ] Separate gigantic `api` to submodules and refactor the routing +- [ ] Add tests for the validators +- [ ] Add integrational test for the whole cycle +- [ ] Add validations of definitions with inheritance +- [ ] Add proper response validation (this `list` hack is so weird) +- [ ] Fix enum validation. It doesn't work correctly when the parans is in qs/header diff --git a/modules/swagger-codegen/src/main/resources/erlang-server/api.mustache b/modules/swagger-codegen/src/main/resources/erlang-server/api.mustache index 0ceb6a41dbb..815d8535215 100644 --- a/modules/swagger-codegen/src/main/resources/erlang-server/api.mustache +++ b/modules/swagger-codegen/src/main/resources/erlang-server/api.mustache @@ -10,8 +10,8 @@ {{#operations}}{{#operation}} request_params('{{operationId}}') -> [{{#allParams}}{{^isBodyParam}} - "{{baseName}}"{{/isBodyParam}}{{#isBodyParam}} - "{{dataType}}"{{/isBodyParam}}{{#hasMore}},{{/hasMore}}{{/allParams}} + '{{baseName}}'{{/isBodyParam}}{{#isBodyParam}} + '{{dataType}}'{{/isBodyParam}}{{#hasMore}},{{/hasMore}}{{/allParams}} ]; {{/operation}}{{/operations}}{{/apis}}{{/apiInfo}} request_params(_) -> @@ -19,7 +19,7 @@ request_params(_) -> {{#apiInfo}}{{#apis}} {{#operations}}{{#operation}}{{#allParams}} -request_param_info('{{operationId}}', {{^isBodyParam}}"{{baseName}}"{{/isBodyParam}}{{#isBodyParam}}"{{dataType}}"{{/isBodyParam}}) -> +request_param_info('{{operationId}}', {{^isBodyParam}}'{{baseName}}'{{/isBodyParam}}{{#isBodyParam}}'{{dataType}}'{{/isBodyParam}}) -> #{ source => {{#isQueryParam}}qs_val{{/isQueryParam}} {{#isPathParam}}binding{{/isPathParam}} {{#isHeaderParam}}header{{/isHeaderParam}}{{#isBodyParam}}body{{/isBodyParam}}, rules => [{{#isString}} @@ -33,8 +33,7 @@ request_param_info('{{operationId}}', {{^isBodyParam}}"{{baseName}}"{{/isBodyPar {type, 'boolean'}, {{/isBoolean}}{{#isDate}} {type, 'date'}, {{/isDate}}{{#isDateTime}} {type, 'datetime'}, {{/isDateTime}}{{#isEnum}} - {enum, [{{#allowableValues}}{{#values}}{{.}}{{/values}}{{/allowableValues}}] },{{/isEnum}}{{#required}} - required, {{/required}}{{#maximum}} + {enum, [{{#allowableValues}}{{#values}}{{.}}{{/values}}{{/allowableValues}}] },{{/isEnum}}{{#maximum}} {max, {{maximum}} }, {{/maximum}}{{#exclusiveMaximum}} {exclusive_max, {{exclusiveMaximum}} }, {{/exclusiveMaximum}}{{#minimum}} {min, {{minimum}} }, {{/minimum}}{{#exclusiveMinimum}} @@ -42,8 +41,9 @@ request_param_info('{{operationId}}', {{^isBodyParam}}"{{baseName}}"{{/isBodyPar {max_length, {{maxLength}} }, {{/maxLength}}{{#minLength}} {min_length, {{minLength}} }, {{/minLength}}{{#pattern}} {pattern, "{{pattern}}" }, {{/pattern}}{{#isBodyParam}} - schema, {{/isBodyParam}} - ok + schema, {{/isBodyParam}}{{#required}} + required{{/required}}{{^required}} + not_required{{/required}} ] }; {{/allParams}}{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}} @@ -79,7 +79,7 @@ populate_request_param(OperationID, Name, Req0, ValidatorState) -> {{#operations}}{{#operation}} {{#responses}} validate_response('{{operationId}}', {{code}}, Body, ValidatorState) -> - validate_response_body("{{dataType}}", "{{baseType}}", Body, ValidatorState); + validate_response_body('{{dataType}}', '{{baseType}}', Body, ValidatorState); {{/responses}} {{/operation}}{{/operations}}{{/apis}}{{/apiInfo}} @@ -92,7 +92,7 @@ validate_response_body(undefined, _, Body, _) -> _ -> throw(invalid_response) end; -validate_response_body("list", ReturnBaseType, Body, ValidatorState) -> +validate_response_body('list', ReturnBaseType, Body, ValidatorState) -> [ validate(schema, ReturnBaseType, Item, ValidatorState) || Item <- Body]; @@ -107,6 +107,9 @@ validate( Rule = required, Name, Value, _ValidatorState) -> _ -> ok end; +validate(not_required, _Name, _Value, _ValidatorState) -> + ok; + validate( _, _Name, undefined, _ValidatorState) -> ok; @@ -204,17 +207,15 @@ validate( Rule = {pattern, Pattern}, Name, Value, _ValidatorState) -> end; validate( Rule = schema, Name, Value, ValidatorState) -> - Definition = list_to_binary("#/definitions/" ++ Name), + Definition = list_to_binary("#/definitions/" ++ {{packageName}}_utils:to_list(Name)), try - validate_with_schema(Value, Definition, ValidatorState) + validate_with_schema(Value, Definition, ValidatorState), + ok catch _:_ -> validation_error(Rule, Name) end; -validate(ok, _Name, _Value, _ValidatorState) -> - ok; - validate(Rule, Name, Value, _ValidatorState) -> io:format("Skipping unknown validation: ~p~n", [{Rule, Name, Value}]), ok. @@ -229,17 +230,17 @@ get_value(body, _Name, Req0) -> get_value(qs_val, Name, Req0) -> {QS, Req} = cowboy_req:qs_vals(Req0), - Value = {{packageName}}_utils:get_opt({{packageName}}_utils:string_to_qs(Name), QS), + Value = {{packageName}}_utils:get_opt({{packageName}}_utils:to_qs(Name), QS), {Value, Req}; get_value(header, Name, Req0) -> {Headers, Req} = cowboy_req:headers(Req0), - Value = {{packageName}}_utils:get_opt({{packageName}}_utils:string_to_header(Name), Headers), + Value = {{packageName}}_utils:get_opt({{packageName}}_utils:to_header(Name), Headers), {Value, Req}; get_value(binding, Name, Req0) -> {Bindings, Req} = cowboy_req:bindings(Req0), - Value = {{packageName}}_utils:get_opt({{packageName}}_utils:string_to_binding(Name), Bindings), + Value = {{packageName}}_utils:get_opt({{packageName}}_utils:to_binding(Name), Bindings), {Value, Req}. prepare_body(Body) -> diff --git a/modules/swagger-codegen/src/main/resources/erlang-server/auth.mustache b/modules/swagger-codegen/src/main/resources/erlang-server/auth.mustache index b6a29a9aa26..624e86cf064 100644 --- a/modules/swagger-codegen/src/main/resources/erlang-server/auth.mustache +++ b/modules/swagger-codegen/src/main/resources/erlang-server/auth.mustache @@ -20,7 +20,7 @@ authorize_api_key(From, KeyParam, OperationID, Req0, LogicHandler) -> get_api_key(header, KeyParam, Req0) -> {Headers, Req} = cowboy_req:headers(Req0), - {swagger_utils:get_opt({{packageName}}_utils:string_to_header(KeyParam), Headers), Req}; + {swagger_utils:get_opt({{packageName}}_utils:to_header(KeyParam), Headers), Req}; get_api_key(qs_val, KeyParam, Req0) -> {QS, Req} = cowboy_req:qs_vals(Req0), diff --git a/modules/swagger-codegen/src/main/resources/erlang-server/handler.mustache b/modules/swagger-codegen/src/main/resources/erlang-server/handler.mustache index ff32cedc81b..e000ed6b78e 100644 --- a/modules/swagger-codegen/src/main/resources/erlang-server/handler.mustache +++ b/modules/swagger-codegen/src/main/resources/erlang-server/handler.mustache @@ -114,8 +114,7 @@ handle_request_json( {halt, Req, State} end; {error, _Reason, Req1} -> - {ok, Req} = cowboy_req:reply(400, [], <<"">>, Req1), - {halt, Req, State} + {false, Req1, State} end. validate_headers(_, Req) -> {true, Req}. diff --git a/modules/swagger-codegen/src/main/resources/erlang-server/utils.mustache b/modules/swagger-codegen/src/main/resources/erlang-server/utils.mustache index 6a44dec9e1e..6905501022a 100644 --- a/modules/swagger-codegen/src/main/resources/erlang-server/utils.mustache +++ b/modules/swagger-codegen/src/main/resources/erlang-server/utils.mustache @@ -4,10 +4,12 @@ -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([string_to_header/1]). --export([string_to_qs/1]). --export([string_to_binding/1]). +-export([to_header/1]). +-export([to_qs/1]). +-export([to_binding/1]). -export([get_opt/2]). -export([get_opt/3]). -export([priv_dir/0]). @@ -59,14 +61,16 @@ set_resp_headers([{K, V} | T], Req0) -> Req = cowboy_req:set_resp_header(K, V, Req0), set_resp_headers(T, Req). -string_to_header(Name) -> - list_to_binary(string:to_lower(Name)). +to_header(Name) -> + Prepared = to_binary(Name), + to_lower(Prepared). -string_to_qs(Name) -> - list_to_atom(string:to_lower(Name)). +to_qs(Name) -> + to_binary(Name). -string_to_binding(Name) -> - list_to_atom(string:to_lower(Name)). +to_binding(Name) -> + Prepared = to_binary(Name), + binary_to_atom(Prepared, utf8). get_opt(Key, Opts) -> get_opt(Key, Opts, undefined). @@ -113,3 +117,46 @@ test_priv_dir(Path) -> _ -> 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. From 10e9b5990d6b6485081c26471027d32b931b33d7 Mon Sep 17 00:00:00 2001 From: Artem Ocheredko Date: Fri, 12 Aug 2016 15:09:17 +0300 Subject: [PATCH 12/16] CAPI-23 Add auth context to request handling (#2) * CAPI-23 Fix routing to support different paths in one handler. Add auth context to request handling. Add an opportunity to pass custom middlewares to the server --- .../default_logic_handler.mustache | 6 +-- .../resources/erlang-server/handler.mustache | 9 ++-- .../erlang-server/logic_handler.mustache | 5 +- .../resources/erlang-server/router.mustache | 48 +++++++++---------- .../resources/erlang-server/server.mustache | 23 +++++++-- 5 files changed, 54 insertions(+), 37 deletions(-) diff --git a/modules/swagger-codegen/src/main/resources/erlang-server/default_logic_handler.mustache b/modules/swagger-codegen/src/main/resources/erlang-server/default_logic_handler.mustache index 56ce9789ff8..f42bdc04092 100644 --- a/modules/swagger-codegen/src/main/resources/erlang-server/default_logic_handler.mustache +++ b/modules/swagger-codegen/src/main/resources/erlang-server/default_logic_handler.mustache @@ -2,7 +2,7 @@ -behaviour({{packageName}}_logic_handler). --export([handle_request/2]). +-export([handle_request/3]). {{#authMethods}} {{#isApiKey}} -export([authorize_api_key/2]). @@ -15,6 +15,6 @@ authorize_api_key(_, _) -> {true, #{}}. {{/isApiKey}} {{/authMethods}} -handle_request(OperationID, Req) -> - io:format(user, "Got request to process: ~p~n", [{OperationID, Req}]), +handle_request(OperationID, Req, Context) -> + io:format(user, "Got request to process: ~p~n", [{OperationID, Req, Context}]), {501, [], <<"Not Implemented">>}. diff --git a/modules/swagger-codegen/src/main/resources/erlang-server/handler.mustache b/modules/swagger-codegen/src/main/resources/erlang-server/handler.mustache index e000ed6b78e..db72b2739b4 100644 --- a/modules/swagger-codegen/src/main/resources/erlang-server/handler.mustache +++ b/modules/swagger-codegen/src/main/resources/erlang-server/handler.mustache @@ -28,9 +28,9 @@ init(_Transport, Req, Opts) -> {upgrade, protocol, cowboy_rest, Req, Opts}. -rest_init(Req0, [LogicHandler, ValidatorState]) -> +rest_init(Req0, [Operations, LogicHandler, ValidatorState]) -> {Method, Req} = cowboy_req:method(Req0), - OperationID = {{packageName}}_router:get_operation_id(Method, ?MODULE), + OperationID = maps:get(Method, Operations, undefined), State = #state{ operation_id = OperationID, logic_handler = LogicHandler, @@ -101,12 +101,13 @@ handle_request_json( State = #state{ operation_id = OperationID, logic_handler = LogicHandler, - validator_state = ValidatorState + validator_state = ValidatorState, + context = Context } ) -> case {{packageName}}_api:populate_request(OperationID, Req0, ValidatorState) of {ok, Populated, Req1} -> - case LogicHandler:handle_request(OperationID, Populated) of + case LogicHandler:handle_request(OperationID, Populated, Context) of {Code, Headers, Body} -> {{packageName}}_api:validate_response(OperationID, Code, Body , ValidatorState), PreparedBody = jsx:encode(Body), diff --git a/modules/swagger-codegen/src/main/resources/erlang-server/logic_handler.mustache b/modules/swagger-codegen/src/main/resources/erlang-server/logic_handler.mustache index 8ee01cd0eb5..da1d1b516f9 100644 --- a/modules/swagger-codegen/src/main/resources/erlang-server/logic_handler.mustache +++ b/modules/swagger-codegen/src/main/resources/erlang-server/logic_handler.mustache @@ -1,8 +1,7 @@ -module({{packageName}}_logic_handler). -{{#hasAuthMethods}} -type context() :: #{binary() => any()}. -{{/hasAuthMethods}} + {{#authMethods}} {{#isApiKey}} -callback authorize_api_key(ApiKey :: binary(), OperationID :: atom()) -> Result :: boolean() | {boolean(), context()}. @@ -10,4 +9,4 @@ {{/authMethods}} --callback handle_request(OperationID :: atom(), Request :: any()) -> {Status :: integer(), [Header :: binary()], Body :: any()}. +-callback handle_request(OperationID :: atom(), Request :: any(), Context :: context()) -> {Status :: integer(), [Header :: binary()], Body :: any()}. diff --git a/modules/swagger-codegen/src/main/resources/erlang-server/router.mustache b/modules/swagger-codegen/src/main/resources/erlang-server/router.mustache index 03a92431acc..55755ad0a9a 100644 --- a/modules/swagger-codegen/src/main/resources/erlang-server/router.mustache +++ b/modules/swagger-codegen/src/main/resources/erlang-server/router.mustache @@ -1,49 +1,49 @@ -module({{packageName}}_router). -export([get_paths/1]). --export([get_operations/0]). --export([get_operation_id/2]). get_paths(LogicHandler) -> ValidatorState = prepare_validator(), - Paths = lists:usort(maps:fold( - fun(_, #{path := Path, handler := Handler}, Acc) -> - [{Path, Handler} | Acc] + PreparedPaths = maps:fold( + fun(Path, #{operations := Operations, handler := Handler}, Acc) -> + [{Path, Handler, Operations} | Acc] end, [], - get_operations() - )), + group_paths() + ), [ {'_', - [{P, H, [LogicHandler, ValidatorState]} || {P, H} <- 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 => "{{path}}", + path => "{{basePathWithoutHost}}{{path}}", method => <<"{{httpMethod}}">>, handler => '{{classname}}' }{{#hasMore}},{{/hasMore}}{{/operation}}{{#hasMore}},{{/hasMore}}{{/operations}}{{/apis}}{{/apiInfo}} }. -get_operation_id(Method, Handler) -> - get_operation_id(Method, Handler, get_operations()). - -get_operation_id(Method, Handler, Operations) -> - maps:fold( - fun - (OperationID, #{method := M, handler := H}, _) when H =:= Handler, M =:= Method -> - OperationID; - (_, _, Acc) -> - Acc - end, - undefined, - Operations - ). - prepare_validator() -> R = jsx:decode(element(2, file:read_file(get_swagger_path()))), jesse_state:new(R, [{default_schema_ver, <<"http://json-schema.org/draft-04/schema#">>}]). diff --git a/modules/swagger-codegen/src/main/resources/erlang-server/server.mustache b/modules/swagger-codegen/src/main/resources/erlang-server/server.mustache index 2d2e12f04f2..25ce859c08e 100644 --- a/modules/swagger-codegen/src/main/resources/erlang-server/server.mustache +++ b/modules/swagger-codegen/src/main/resources/erlang-server/server.mustache @@ -14,7 +14,8 @@ child_spec(Id, #{ AcceptorsPool = ?DEFAULT_ACCEPTORS_POOLSIZE, {Transport, TransportOpts} = get_socket_transport(Ip, Port, NetOpts), LogicHandler = maps:get(logic_handler, Params, ?DEFAULT_LOGIC_HANDLER), - CowboyOpts = get_cowboy_config(LogicHandler), + ExtraOpts = maps:get(cowboy_extra_opts, Params, []), + CowboyOpts = get_cowboy_config(LogicHandler, ExtraOpts), ranch:child_spec({?MODULE, Id}, AcceptorsPool, Transport, TransportOpts, cowboy_protocol, CowboyOpts). @@ -30,6 +31,22 @@ get_socket_transport(Ip, Port, Options) -> {ranch_tcp, Opts} end. -get_cowboy_config(LogicHandler) -> +get_cowboy_config(LogicHandler, ExtraOpts) -> + get_cowboy_config(LogicHandler, ExtraOpts, []). + +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, [{env, NewEnv} | Opts]); + +get_cowboy_config(LogicHandler, [O | Rest], Opts) -> + get_cowboy_config(LogicHandler, Rest, [O | Opts]). + +get_default_dispatch(LogicHandler) -> Paths = swagger_router:get_paths(LogicHandler), - [{env, [{dispatch, cowboy_router:compile(Paths)}]}]. + {dispatch, cowboy_router:compile(Paths)}. From 99c7dc2d89d75c40087159decbdcfe27339e7f6b Mon Sep 17 00:00:00 2001 From: Artem Ocheredko Date: Fri, 12 Aug 2016 15:09:43 +0300 Subject: [PATCH 13/16] CAPI-31 Add enum validation and some minor fixes (#4) --- .../main/resources/erlang-server/api.mustache | 29 +++++++++++++------ 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/modules/swagger-codegen/src/main/resources/erlang-server/api.mustache b/modules/swagger-codegen/src/main/resources/erlang-server/api.mustache index 815d8535215..9da544fe73d 100644 --- a/modules/swagger-codegen/src/main/resources/erlang-server/api.mustache +++ b/modules/swagger-codegen/src/main/resources/erlang-server/api.mustache @@ -33,7 +33,7 @@ request_param_info('{{operationId}}', {{^isBodyParam}}'{{baseName}}'{{/isBodyPar {type, 'boolean'}, {{/isBoolean}}{{#isDate}} {type, 'date'}, {{/isDate}}{{#isDateTime}} {type, 'datetime'}, {{/isDateTime}}{{#isEnum}} - {enum, [{{#allowableValues}}{{#values}}{{.}}{{/values}}{{/allowableValues}}] },{{/isEnum}}{{#maximum}} + {enum, [{{#allowableValues}}{{#values}}'{{.}}'{{^-last}}, {{/-last}}{{/values}}{{/allowableValues}}] },{{/isEnum}}{{#maximum}} {max, {{maximum}} }, {{/maximum}}{{#exclusiveMaximum}} {exclusive_max, {{exclusiveMaximum}} }, {{/exclusiveMaximum}}{{#minimum}} {min, {{minimum}} }, {{/minimum}}{{#exclusiveMinimum}} @@ -117,8 +117,8 @@ validate( Rule = {type, 'integer'}, Name, Value, _ValidatorState) -> try {ok, {{packageName}}_utils:to_int(Value)} catch - _:_ -> - validation_error(Rule, Name) + _:Error -> + validation_error(Rule, {Name, Error}) end; validate( Rule = {type, 'float'}, Name, Value, _ValidatorState) -> @@ -140,9 +140,14 @@ validate( _Rule = {type, 'boolean'}, _Name, Value, _ValidatorState) when is_bool validate( Rule = {type, 'boolean'}, Name, Value, _ValidatorState) -> V = binary_to_lower(Value), - case binary_to_existing_atom(V, utf8) of - B when is_boolean(B) -> {ok, B}; - _ -> validation_error(Rule, Name) + 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) -> @@ -158,9 +163,15 @@ validate( Rule = {type, 'datetime'}, Name, Value, _ValidatorState) -> end; validate( Rule = {enum, Values}, Name, Value, _ValidatorState) -> - case lists:member(Value, Values) of - true -> ok; - false -> validation_error(Rule, Name) + 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) -> From 10cbb79e6fbf085979c2f144862c1749fdfb42da Mon Sep 17 00:00:00 2001 From: Artem Ocheredko Date: Fri, 12 Aug 2016 17:31:21 +0300 Subject: [PATCH 14/16] CAPI-31 Fix turbo fuck up with additional params (#5) --- .../main/resources/erlang-server/server.mustache | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/modules/swagger-codegen/src/main/resources/erlang-server/server.mustache b/modules/swagger-codegen/src/main/resources/erlang-server/server.mustache index 25ce859c08e..67c9a4b862d 100644 --- a/modules/swagger-codegen/src/main/resources/erlang-server/server.mustache +++ b/modules/swagger-codegen/src/main/resources/erlang-server/server.mustache @@ -32,7 +32,7 @@ get_socket_transport(Ip, Port, Options) -> end. get_cowboy_config(LogicHandler, ExtraOpts) -> - get_cowboy_config(LogicHandler, ExtraOpts, []). + get_cowboy_config(LogicHandler, ExtraOpts, get_default_opts(LogicHandler)). get_cowboy_config(_LogicHandler, [], Opts) -> Opts; @@ -42,11 +42,17 @@ get_cowboy_config(LogicHandler, [{env, Env} | Rest], Opts) -> undefined -> [get_default_dispatch(LogicHandler) | Env]; _ -> Env end, - get_cowboy_config(LogicHandler, Rest, [{env, NewEnv} | Opts]); + get_cowboy_config(LogicHandler, Rest, store_key(env, NewEnv, Opts)); -get_cowboy_config(LogicHandler, [O | Rest], Opts) -> - get_cowboy_config(LogicHandler, Rest, [O | Opts]). +get_cowboy_config(LogicHandler, [{Key, Value}| Rest], Opts) -> + get_cowboy_config(LogicHandler, Rest, store_key(Key, Value, Opts)). get_default_dispatch(LogicHandler) -> - Paths = swagger_router:get_paths(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) -> + lists:keystore(Key, 1, Opts, {Key, Value}). From eb116398250cfe2bcaf531fc2edac857bd252493 Mon Sep 17 00:00:00 2001 From: Artem Ocheredko Date: Wed, 31 Aug 2016 20:31:41 +0300 Subject: [PATCH 15/16] Capi 23/fix/basic logging (#6) * CAPI-23 Add understandable messages in case of bad requests. Add specs to shut up dialyzer and add some minor code refactoring --- .../main/resources/erlang-server/api.mustache | 162 ++++++++++++------ .../resources/erlang-server/auth.mustache | 54 ++++-- .../default_logic_handler.mustache | 16 +- .../resources/erlang-server/handler.mustache | 145 ++++++++++++++-- .../erlang-server/logic_handler.mustache | 42 ++++- .../resources/erlang-server/router.mustache | 20 ++- .../resources/erlang-server/server.mustache | 18 +- .../resources/erlang-server/utils.mustache | 11 ++ 8 files changed, 369 insertions(+), 99 deletions(-) diff --git a/modules/swagger-codegen/src/main/resources/erlang-server/api.mustache b/modules/swagger-codegen/src/main/resources/erlang-server/api.mustache index 9da544fe73d..355cfad6dae 100644 --- a/modules/swagger-codegen/src/main/resources/erlang-server/api.mustache +++ b/modules/swagger-codegen/src/main/resources/erlang-server/api.mustache @@ -5,7 +5,12 @@ -export([populate_request/3]). -export([validate_response/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}}') -> @@ -17,31 +22,56 @@ request_params('{{operationId}}') -> 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}}, 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}} + {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}} + {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}} ] @@ -50,6 +80,14 @@ request_param_info('{{operationId}}', {{^isBodyParam}}'{{baseName}}'{{/isBodyPar 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, #{}). @@ -74,7 +112,12 @@ populate_request_param(OperationID, Name, Req0, ValidatorState) -> {error, Reason, Req} 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}} @@ -86,12 +129,6 @@ validate_response('{{operationId}}', {{code}}, Body, ValidatorState) -> validate_response(_OperationID, _Code, _Body, _ValidatorState) -> ok. -validate_response_body(undefined, _, Body, _) -> - case Body of - #{} -> ok; - _ -> throw(invalid_response) - end; - validate_response_body('list', ReturnBaseType, Body, ValidatorState) -> [ validate(schema, ReturnBaseType, Item, ValidatorState) @@ -101,7 +138,7 @@ validate_response_body(_, ReturnBaseType, Body, ValidatorState) -> validate(schema, ReturnBaseType, Body, ValidatorState). %%% -validate( Rule = required, Name, Value, _ValidatorState) -> +validate(Rule = required, Name, Value, _ValidatorState) -> case Value of undefined -> validation_error(Rule, Name); _ -> ok @@ -110,35 +147,35 @@ validate( Rule = required, Name, Value, _ValidatorState) -> validate(not_required, _Name, _Value, _ValidatorState) -> ok; -validate( _, _Name, undefined, _ValidatorState) -> +validate(_, _Name, undefined, _ValidatorState) -> ok; -validate( Rule = {type, 'integer'}, Name, Value, _ValidatorState) -> +validate(Rule = {type, 'integer'}, Name, Value, _ValidatorState) -> try {ok, {{packageName}}_utils:to_int(Value)} catch - _:Error -> - validation_error(Rule, {Name, Error}) + error:badarg -> + validation_error(Rule, Name) end; -validate( Rule = {type, 'float'}, Name, Value, _ValidatorState) -> +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) -> +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) -> +validate(_Rule = {type, 'boolean'}, _Name, Value, _ValidatorState) when is_boolean(Value) -> {ok, Value}; -validate( Rule = {type, 'boolean'}, Name, Value, _ValidatorState) -> +validate(Rule = {type, 'boolean'}, Name, Value, _ValidatorState) -> V = binary_to_lower(Value), try case binary_to_existing_atom(V, utf8) of @@ -150,19 +187,19 @@ validate( Rule = {type, 'boolean'}, Name, Value, _ValidatorState) -> validation_error(Rule, Name) end; -validate( Rule = {type, 'date'}, Name, Value, _ValidatorState) -> +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) -> +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) -> +validate(Rule = {enum, Values}, Name, Value, _ValidatorState) -> try FormattedValue = erlang:binary_to_existing_atom(Value, utf8), case lists:member(FormattedValue, Values) of @@ -174,65 +211,84 @@ validate( Rule = {enum, Values}, Name, Value, _ValidatorState) -> validation_error(Rule, Name) end; -validate( Rule = {max, Max}, Name, Value, _ValidatorState) -> +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) -> +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) -> +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) -> +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) -> +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) -> +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) -> +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) -> +validate(Rule = schema, Name, Value, ValidatorState) -> Definition = list_to_binary("#/definitions/" ++ {{packageName}}_utils:to_list(Name)), try - validate_with_schema(Value, Definition, ValidatorState), + _ = validate_with_schema(Value, Definition, ValidatorState), ok catch - _:_ -> - validation_error(Rule, Name) + 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) -> - io:format("Skipping unknown validation: ~p~n", [{Rule, Name, Value}]), - ok. +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(Name, Violated) -> - throw({wrong_param, Name, Violated}). +validation_error(ViolatedRule, Name, Info) -> + throw({wrong_param, Name, ViolatedRule, Info}). get_value(body, _Name, Req0) -> {ok, Body, Req} = cowboy_req:body(Req0), diff --git a/modules/swagger-codegen/src/main/resources/erlang-server/auth.mustache b/modules/swagger-codegen/src/main/resources/erlang-server/auth.mustache index 624e86cf064..2fd7bd8652c 100644 --- a/modules/swagger-codegen/src/main/resources/erlang-server/auth.mustache +++ b/modules/swagger-codegen/src/main/resources/erlang-server/auth.mustache @@ -2,29 +2,49 @@ -export([authorize_api_key/5]). -authorize_api_key(From, KeyParam, OperationID, Req0, LogicHandler) -> - {ApiKey, Req} = get_api_key(From, KeyParam, Req0), - case ApiKey of - undefined -> - AuthHeader = <<"">>, - {false, AuthHeader, Req}; - _ -> - case LogicHandler:authorize_api_key(ApiKey, OperationID) of - {true, Context} -> - {true, Context, Req}; - false -> - AuthHeader = <<"">>, - {false, AuthHeader, Req} - end - end. +-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 + {true, Context} -> + true, Context, Req}; + false -> + AuthHeader = <<"">>, + {false, AuthHeader, Req} + end + end. get_api_key(header, KeyParam, Req0) -> {Headers, Req} = cowboy_req:headers(Req0), - {swagger_utils:get_opt({{packageName}}_utils:to_header(KeyParam), Headers), Req}; + { + swagger_utils:get_opt( + {{packageName}}_utils:to_header(KeyParam), + Headers + ), + Req + }; get_api_key(qs_val, KeyParam, Req0) -> {QS, Req} = cowboy_req:qs_vals(Req0), - {swagger_utils:get_opt(KeyParam, QS), Req}. + { {{packageName}}_utils:get_opt(KeyParam, QS), Req}. diff --git a/modules/swagger-codegen/src/main/resources/erlang-server/default_logic_handler.mustache b/modules/swagger-codegen/src/main/resources/erlang-server/default_logic_handler.mustache index f42bdc04092..3be26911c74 100644 --- a/modules/swagger-codegen/src/main/resources/erlang-server/default_logic_handler.mustache +++ b/modules/swagger-codegen/src/main/resources/erlang-server/default_logic_handler.mustache @@ -11,10 +11,22 @@ {{#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 :: #{}}. + handle_request(OperationID, Req, Context) -> - io:format(user, "Got request to process: ~p~n", [{OperationID, Req, Context}]), - {501, [], <<"Not Implemented">>}. + error_logger:error_msg( + "Got not implemented request to process: ~p~n", + [{OperationID, Req, Context}] + ), + {501, [], #{}}. diff --git a/modules/swagger-codegen/src/main/resources/erlang-server/handler.mustache b/modules/swagger-codegen/src/main/resources/erlang-server/handler.mustache index db72b2739b4..4559bc2962a 100644 --- a/modules/swagger-codegen/src/main/resources/erlang-server/handler.mustache +++ b/modules/swagger-codegen/src/main/resources/erlang-server/handler.mustache @@ -19,18 +19,29 @@ -export([handle_request_json/2]). -record(state, { - operation_id, - logic_handler, - validator_state, - context=#{} + operation_id :: {{packageName}}_api:operation_id(), + logic_handler :: atom(), + validator_state :: jesse_state:state(), + context=#{} :: #{} }). +-type state() :: state(). + +-spec init(TransportName :: atom(), Req :: cowboy_req:req(), Opts :: {{packageName}}_router:init_opts()) -> + {upgrade, protocol, cowboy_rest, Req :: cowboy_req:req(), Opts :: {{packageName}}_router:init_opts()}. + init(_Transport, Req, Opts) -> {upgrade, protocol, cowboy_rest, Req, Opts}. -rest_init(Req0, [Operations, LogicHandler, ValidatorState]) -> +-spec rest_init(Req :: cowboy_req:req(), Opts :: {{packageName}}_router:init_opts()) -> + {ok, Req :: cowboy_req:req(), State :: state()}. + +rest_init(Req0, {Operations, LogicHandler, ValidatorState}) -> {Method, Req} = cowboy_req:method(Req0), OperationID = maps:get(Method, Operations, undefined), + + error_logger:info_msg("Attempt to process operation: ~p", [OperationID]), + State = #state{ operation_id = OperationID, logic_handler = LogicHandler, @@ -38,19 +49,46 @@ rest_init(Req0, [Operations, LogicHandler, ValidatorState]) -> }, {ok, 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}}'}) -> +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}} -is_authorized(Req0, State = #state{operation_id = '{{operationId}}' = OperationID, logic_handler = LogicHandler}) -> +is_authorized( + Req0, + State = #state{ + operation_id = '{{operationId}}' = OperationID, + logic_handler = LogicHandler + } +) -> {{#authMethods}} {{#isApiKey}} From = {{#isKeyInQuery}}qs_val{{/isKeyInQuery}}{{#isKeyInHeader}}header{{/isKeyInHeader}}, - case {{packageName}}_auth:authorize_api_key(From, "{{keyParamName}}", OperationID, Req0, LogicHandler) of + 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; @@ -60,13 +98,27 @@ is_authorized(Req0, State = #state{operation_id = '{{operationId}}' = OperationI is_authorized(Req, State) -> {{false, <<"">>}, Req, State}. +-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}}'}) -> +valid_content_headers( + Req0, + State = #state{ + operation_id = '{{operationId}}' + } +) -> Headers = [{{#headerParams}}"{{baseName}}"{{#hasMore}},{{/hasMore}}{{/headerParams}}], {Result, Req} = validate_headers(Headers, Req0), {Result, Req, State}; @@ -74,28 +126,77 @@ valid_content_headers(Req0, State = #state{operation_id = '{{operationId}}'}) -> 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() :: {halt, 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}} -> + {ok, Req} = cowboy_req:reply(Code, Headers, Body, Req0), + {halt, Req, State}; + {error, Message} -> + error_logger:error_msg("Unable to process request for ~p: ~p", [OperationID, Message]), + + {ok, Req} = cowboy_req:reply(400, Req0), + {halt, Req, State} + end. + +-spec handle_request_json(cowboy_req:req(), state()) -> {halt, cowboy_req:req(), state()}. + handle_request_json( Req0, State = #state{ @@ -107,15 +208,23 @@ handle_request_json( ) -> case {{packageName}}_api:populate_request(OperationID, Req0, ValidatorState) of {ok, Populated, Req1} -> - case LogicHandler:handle_request(OperationID, Populated, Context) of - {Code, Headers, Body} -> - {{packageName}}_api:validate_response(OperationID, Code, Body , ValidatorState), - PreparedBody = jsx:encode(Body), - {ok, Req} = cowboy_req:reply(Code, Headers, PreparedBody, Req1), - {halt, Req, State} - end; - {error, _Reason, Req1} -> - {false, Req1, State} + {Code, Headers, Body} = {{packageName}}_logic_handler:handle_request( + LogicHandler, + OperationID, + Populated, + Context + ), + _ = {{packageName}}_api:validate_response( + OperationID, + Code, + Body, + ValidatorState + ), + PreparedBody = jsx:encode(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}. diff --git a/modules/swagger-codegen/src/main/resources/erlang-server/logic_handler.mustache b/modules/swagger-codegen/src/main/resources/erlang-server/logic_handler.mustache index da1d1b516f9..cb4ba201bf4 100644 --- a/modules/swagger-codegen/src/main/resources/erlang-server/logic_handler.mustache +++ b/modules/swagger-codegen/src/main/resources/erlang-server/logic_handler.mustache @@ -1,12 +1,50 @@ -module({{packageName}}_logic_handler). +-export([handle_request/4]). +{{#authMethods}} + {{#isApiKey}} +-export([authorize_api_key/3]). + {{/isApiKey}} +{{/authMethods}} -type context() :: #{binary() => any()}. +-type handler_response() ::{ + Status :: cowboy:http_status(), + Headers :: cowboy:http_headers(), + Body :: #{} +}. + +-export_type([handler_response/0]). {{#authMethods}} {{#isApiKey}} --callback authorize_api_key(ApiKey :: binary(), OperationID :: atom()) -> Result :: boolean() | {boolean(), context()}. +-callback authorize_api_key( + OperationID :: {{packageName}}_api:operation_id(), + ApiKey :: binary() +) -> + Result :: boolean() | {boolean(), context()}. {{/isApiKey}} {{/authMethods}} --callback handle_request(OperationID :: atom(), Request :: any(), Context :: context()) -> {Status :: integer(), [Header :: binary()], Body :: any()}. +-callback handle_request(OperationID :: {{packageName}}_api:operation_id(), Request :: any(), Context :: context()) -> + handler_response(). + +-spec handle_request( + Handler :: atom(), + OperationID :: {{packageName}}_api:operation_id(), + Request :: any(), + 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}} diff --git a/modules/swagger-codegen/src/main/resources/erlang-server/router.mustache b/modules/swagger-codegen/src/main/resources/erlang-server/router.mustache index 55755ad0a9a..9c5ff89b229 100644 --- a/modules/swagger-codegen/src/main/resources/erlang-server/router.mustache +++ b/modules/swagger-codegen/src/main/resources/erlang-server/router.mustache @@ -2,6 +2,24 @@ -export([get_paths/1]). +-type operations() :: #{ + Method :: binary() => {{packageName}}_api:operation_id() +}. + +-type init_opts() :: { + Operations :: operations(), + LogicHandler :: atom(), + ValidatorState :: jesse_state:state() +}. + +-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( @@ -13,7 +31,7 @@ get_paths(LogicHandler) -> ), [ {'_', - [{P, H, [O, LogicHandler, ValidatorState]} || {P, H, O} <- PreparedPaths] + [{P, H, {O, LogicHandler, ValidatorState}} || {P, H, O} <- PreparedPaths] } ]. diff --git a/modules/swagger-codegen/src/main/resources/erlang-server/server.mustache b/modules/swagger-codegen/src/main/resources/erlang-server/server.mustache index 67c9a4b862d..90a6388ea5d 100644 --- a/modules/swagger-codegen/src/main/resources/erlang-server/server.mustache +++ b/modules/swagger-codegen/src/main/resources/erlang-server/server.mustache @@ -6,22 +6,28 @@ -export([child_spec/2]). -child_spec(Id, #{ - ip := Ip, +-spec child_spec( ID :: any(), #{ + ip => inet:ip_address(), + port => inet:port_number(), + net_opts => [] +}) -> supervisor:child_spec(). + +child_spec(ID, #{ + ip := IP , port := Port, net_opts := NetOpts } = Params) -> AcceptorsPool = ?DEFAULT_ACCEPTORS_POOLSIZE, - {Transport, TransportOpts} = get_socket_transport(Ip, Port, NetOpts), + {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), - ranch:child_spec({?MODULE, Id}, AcceptorsPool, + ranch:child_spec({?MODULE, ID}, AcceptorsPool, Transport, TransportOpts, cowboy_protocol, CowboyOpts). -get_socket_transport(Ip, Port, Options) -> +get_socket_transport(IP, Port, Options) -> Opts = [ - {ip, Ip}, + {ip, IP}, {port, Port} ], case {{packageName}}_utils:get_opt(ssl, Options) of diff --git a/modules/swagger-codegen/src/main/resources/erlang-server/utils.mustache b/modules/swagger-codegen/src/main/resources/erlang-server/utils.mustache index 6905501022a..b6701add7fc 100644 --- a/modules/swagger-codegen/src/main/resources/erlang-server/utils.mustache +++ b/modules/swagger-codegen/src/main/resources/erlang-server/utils.mustache @@ -54,6 +54,7 @@ to_int(Data) when is_binary(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; @@ -61,20 +62,30 @@ 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; From 6484fa9106dac748f739cb74da90a791993f3790 Mon Sep 17 00:00:00 2001 From: Artem Ocheredko Date: Thu, 1 Sep 2016 20:07:13 +0300 Subject: [PATCH 16/16] CAPI-23 Fix missed bracket in auth module (#7) --- .../src/main/resources/erlang-server/auth.mustache | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/swagger-codegen/src/main/resources/erlang-server/auth.mustache b/modules/swagger-codegen/src/main/resources/erlang-server/auth.mustache index 2fd7bd8652c..bf988fac348 100644 --- a/modules/swagger-codegen/src/main/resources/erlang-server/auth.mustache +++ b/modules/swagger-codegen/src/main/resources/erlang-server/auth.mustache @@ -25,7 +25,7 @@ authorize_api_key(LogicHandler, OperationID, From, KeyParam, Req0) -> ), case Result of {true, Context} -> - true, Context, Req}; + {true, Context, Req}; false -> AuthHeader = <<"">>, {false, AuthHeader, Req}