mirror of
https://github.com/OpenAPITools/openapi-generator.git
synced 2025-07-04 06:30:52 +00:00
Add a new NodeJS Express server generator (#3567)
* create nodejs express esrver * 1st commit of the express.js module. Express server working, api-docs loads properly. No real paths yet * 1st commit of the express.js module. Express server working, api-docs loads properly. No real paths yet (#2839) * Working Express server with successful routing to controllers. * rewrote controllers and services. Haven't tested yet * controllers and services have passed tests successfully * Added documentation * Added documentation * Support for openApi v3, using 'express-openapi-validator' for parsing and validation, and an internal router to pass arguments to controllers and services. /controllers/Pet.js and /services/PetService.js should be used for reverse engineering for future codegen script * update generator and template * update samples * more update * update service, controller * add vendor extensions * some updates to adapt to changes in the generator (removing references to swager); some work on handling file uploads; some work on tests * Update NodeJS server generator and templates based on new output (#3261) * update generator and template * update samples * more update * update service, controller * add vendor extensions * update doc * Changed routing code to follow the following convention: Each path operation has a 'x-openapi-router-controller' and 'x-openapi-router-service'. Automated files will be placed under /controllers and /services respectively. Controller file names will end with 'Controller.js'. Removed swaggerRouter, replaced it with openapiRouter Routing works and simple tests show a return of 200 to requests. * [nodejs-express-server] various updates, fixes (#3319) * various fix * remove dot from service * add space * better method empty argument * remove test service (#3379) * add new doc * 1. routingTests.js runs through all operations described in openapi.yaml and tries calling them, expecting 200 in return. Currently not all tests pass - not supporting xml, and problems with formData 2. Removed old testing files. 3. Added model files - contain data and structure as defined in openapi.yaml. Model.js has static methods relevant to all model files. 4. Changed openapi.yaml to allow running tests easily. * 1. routingTests.js runs through all operations described in openapi.yaml and tries calling them, expecting 200 in return. Currently not all tests pass - not supporting xml, and problems with formData (#3442) 2. Removed old testing files. 3. Added model files - contain data and structure as defined in openapi.yaml. Model.js has static methods relevant to all model files. 4. Changed openapi.yaml to allow running tests easily. * added model classes. Currently as a concept only. Seems like won't be in use * Updated README.md to be a detailed description of the project. Removed test files that are not needed. Removed utils/writer.js which is not needed, and the references to it in the codegen files * Removed redundant file app.js - this file has no benefit at this point. index.js now calls ExpressServer.js directly. Updated files that used to call app.js. Updated README.md accordingly Added a path to call the openapi.yaml, and a test file for all endpoints that are not in the openapi.yaml, ensuring that they return 200. Updated README.md accordingly * Remove test controller (#3575) * remove test controller * add back changes to templates * remove app.js * update wording
This commit is contained in:
parent
fbb2f1e05a
commit
2d7cc778db
@ -730,6 +730,7 @@ Here is a list of template creators:
|
||||
* JAX-RS RestEasy (JBoss EAP): @jfiala
|
||||
* Kotlin: @jimschubert [:heart:](https://www.patreon.com/jimschubert)
|
||||
* Kotlin (Spring Boot): @dr4ke616
|
||||
* NodeJS Express: @YishTish
|
||||
* PHP Laravel: @renepardon
|
||||
* PHP Lumen: @abcsun
|
||||
* PHP Slim: @jfastnacht
|
||||
|
32
bin/nodejs-express-petstore-server.sh
Executable file
32
bin/nodejs-express-petstore-server.sh
Executable file
@ -0,0 +1,32 @@
|
||||
#!/bin/sh
|
||||
|
||||
SCRIPT="$0"
|
||||
echo "# START SCRIPT: $SCRIPT"
|
||||
|
||||
while [ -h "$SCRIPT" ] ; do
|
||||
ls=`ls -ld "$SCRIPT"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
SCRIPT="$link"
|
||||
else
|
||||
SCRIPT=`dirname "$SCRIPT"`/"$link"
|
||||
fi
|
||||
done
|
||||
|
||||
if [ ! -d "${APP_DIR}" ]; then
|
||||
APP_DIR=`dirname "$SCRIPT"`/..
|
||||
APP_DIR=`cd "${APP_DIR}"; pwd`
|
||||
fi
|
||||
|
||||
executable="./modules/openapi-generator-cli/target/openapi-generator-cli.jar"
|
||||
|
||||
if [ ! -f "$executable" ]
|
||||
then
|
||||
mvn -B clean package
|
||||
fi
|
||||
|
||||
# if you've executed sbt assembly previously it will use that instead.
|
||||
export JAVA_OPTS="${JAVA_OPTS} -Xmx1024M -DloggerPath=conf/log4j.properties"
|
||||
ags="generate -t modules/openapi-generator/src/main/resources/nodejs-express-server -i modules/openapi-generator/src/test/resources/2_0/petstore.yaml -g nodejs-express-server -o samples/server/petstore/nodejs-express-server -Dservice $@"
|
||||
|
||||
java $JAVA_OPTS -jar $executable $ags
|
@ -94,6 +94,7 @@ The following generators are available:
|
||||
- [jaxrs-spec](generators/jaxrs-spec.md)
|
||||
- [kotlin-server](generators/kotlin-server.md)
|
||||
- [kotlin-spring](generators/kotlin-spring.md)
|
||||
- [nodejs-express-server](generators/nodejs-express-server.md) (beta)
|
||||
- [nodejs-server-deprecated](generators/nodejs-server-deprecated.md) (deprecated)
|
||||
- [php-laravel](generators/php-laravel.md)
|
||||
- [php-lumen](generators/php-lumen.md)
|
||||
|
14
docs/generators/nodejs-express-server.md
Normal file
14
docs/generators/nodejs-express-server.md
Normal file
@ -0,0 +1,14 @@
|
||||
|
||||
---
|
||||
id: generator-opts-server-nodejs-express-server
|
||||
title: Config Options for nodejs-express-server
|
||||
sidebar_label: nodejs-express-server
|
||||
---
|
||||
|
||||
| Option | Description | Values | Default |
|
||||
| ------ | ----------- | ------ | ------- |
|
||||
|sortParamsByRequiredFlag|Sort method arguments to place required parameters before optional parameters.| |true|
|
||||
|ensureUniqueParams|Whether to ensure parameter names are unique in an operation (rename parameters that are not).| |true|
|
||||
|allowUnicodeIdentifiers|boolean, toggles whether unicode identifiers are allowed in names or not, default is false| |false|
|
||||
|prependFormOrBodyParameters|Add form or body parameters to the beginning of the parameter list.| |false|
|
||||
|serverPort|TCP port to listen on.| |null|
|
@ -0,0 +1,390 @@
|
||||
/*
|
||||
* Copyright 2018 OpenAPI-Generator Contributors (https://openapi-generator.tech)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.openapitools.codegen.languages;
|
||||
|
||||
import com.google.common.collect.ArrayListMultimap;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Multimap;
|
||||
import io.swagger.v3.oas.models.OpenAPI;
|
||||
import io.swagger.v3.oas.models.Operation;
|
||||
import io.swagger.v3.oas.models.PathItem;
|
||||
import io.swagger.v3.oas.models.PathItem.HttpMethod;
|
||||
import io.swagger.v3.oas.models.Paths;
|
||||
import io.swagger.v3.oas.models.info.Info;
|
||||
import org.openapitools.codegen.*;
|
||||
import org.openapitools.codegen.meta.GeneratorMetadata;
|
||||
import org.openapitools.codegen.meta.Stability;
|
||||
import org.openapitools.codegen.utils.URLPathUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URL;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static org.openapitools.codegen.utils.StringUtils.*;
|
||||
|
||||
public class NodeJSExpressServerCodegen extends DefaultCodegen implements CodegenConfig {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(NodeJSExpressServerCodegen.class);
|
||||
public static final String EXPORTED_NAME = "exportedName";
|
||||
public static final String SERVER_PORT = "serverPort";
|
||||
|
||||
protected String apiVersion = "1.0.0";
|
||||
protected String defaultServerPort = "8080";
|
||||
protected String implFolder = "services";
|
||||
protected String projectName = "openapi-server";
|
||||
protected String exportedName;
|
||||
|
||||
public NodeJSExpressServerCodegen() {
|
||||
super();
|
||||
|
||||
generatorMetadata = GeneratorMetadata.newBuilder(generatorMetadata)
|
||||
.stability(Stability.BETA)
|
||||
.build();
|
||||
|
||||
outputFolder = "generated-code/nodejs-express-server";
|
||||
embeddedTemplateDir = templateDir = "nodejs-express-server";
|
||||
|
||||
setReservedWordsLowerCase(
|
||||
Arrays.asList(
|
||||
"break", "case", "class", "catch", "const", "continue", "debugger",
|
||||
"default", "delete", "do", "else", "export", "extends", "finally",
|
||||
"for", "function", "if", "import", "in", "instanceof", "let", "new",
|
||||
"return", "super", "switch", "this", "throw", "try", "typeof", "var",
|
||||
"void", "while", "with", "yield")
|
||||
);
|
||||
|
||||
additionalProperties.put("apiVersion", apiVersion);
|
||||
additionalProperties.put("implFolder", implFolder);
|
||||
|
||||
// no model file
|
||||
modelTemplateFiles.clear();
|
||||
|
||||
apiTemplateFiles.put("controller.mustache", ".js");
|
||||
apiTemplateFiles.put("service.mustache", ".js");
|
||||
|
||||
supportingFiles.add(new SupportingFile("openapi.mustache", "api", "openapi.yaml"));
|
||||
supportingFiles.add(new SupportingFile("config.mustache", "", "config.js"));
|
||||
supportingFiles.add(new SupportingFile("expressServer.mustache", "", "expressServer.js"));
|
||||
supportingFiles.add(new SupportingFile("index.mustache", "", "index.js"));
|
||||
supportingFiles.add(new SupportingFile("logger.mustache", "", "logger.js"));
|
||||
supportingFiles.add(new SupportingFile("eslintrc.mustache", "", ".eslintrc.json"));
|
||||
|
||||
// utils folder
|
||||
supportingFiles.add(new SupportingFile("utils" + File.separator + "openapiRouter.mustache", "utils", "openapiRouter.js"));
|
||||
|
||||
// controllers folder
|
||||
supportingFiles.add(new SupportingFile("controllers" + File.separator + "index.mustache", "controllers", "index.js"));
|
||||
supportingFiles.add(new SupportingFile("controllers" + File.separator + "Controller.mustache", "controllers", "Controller.js"));
|
||||
// service folder
|
||||
supportingFiles.add(new SupportingFile("services" + File.separator + "index.mustache", "services", "index.js"));
|
||||
supportingFiles.add(new SupportingFile("services" + File.separator + "Service.mustache", "services", "Service.js"));
|
||||
|
||||
// do not overwrite if the file is already present
|
||||
writeOptional(outputFolder, new SupportingFile("package.mustache", "", "package.json"));
|
||||
writeOptional(outputFolder, new SupportingFile("README.mustache", "", "README.md"));
|
||||
|
||||
cliOptions.add(new CliOption(SERVER_PORT,
|
||||
"TCP port to listen on."));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String apiPackage() {
|
||||
return "controllers";
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the type of generator.
|
||||
*
|
||||
* @return the CodegenType for this generator
|
||||
* @see org.openapitools.codegen.CodegenType
|
||||
*/
|
||||
@Override
|
||||
public CodegenType getTag() {
|
||||
return CodegenType.SERVER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures a friendly name for the generator. This will be used by the generator
|
||||
* to select the library with the -g flag.
|
||||
*
|
||||
* @return the friendly name for the generator
|
||||
*/
|
||||
@Override
|
||||
public String getName() {
|
||||
return "nodejs-express-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 a NodeJS Express server (alpha). IMPORTANT: this generator may subject to breaking changes without further notice).";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toApiName(String name) {
|
||||
if (name.length() == 0) {
|
||||
return "Default";
|
||||
}
|
||||
return camelize(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toApiFilename(String name) {
|
||||
return toApiName(name) + "Controller";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String apiFilename(String templateName, String tag) {
|
||||
String result = super.apiFilename(templateName, tag);
|
||||
|
||||
if (templateName.equals("service.mustache")) {
|
||||
String stringToMatch = File.separator + "controllers" + File.separator;
|
||||
String replacement = File.separator + implFolder + File.separator;
|
||||
result = result.replaceAll(Pattern.quote(stringToMatch), replacement);
|
||||
|
||||
stringToMatch = "Controller.js";
|
||||
replacement = "Service.js";
|
||||
result = result.replaceAll(Pattern.quote(stringToMatch), replacement);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
@Override
|
||||
protected String implFileFolder(String output) {
|
||||
return outputFolder + File.separator + output + File.separator + apiPackage().replace('.', File.separatorChar);
|
||||
}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Escapes a reserved word as defined in the `reservedWords` array. Handle escaping
|
||||
* those terms here. This logic is only called if a variable matches the reserved words
|
||||
*
|
||||
* @return the escaped term
|
||||
*/
|
||||
@Override
|
||||
public String escapeReservedWord(String name) {
|
||||
if (this.reservedWordsMappings().containsKey(name)) {
|
||||
return this.reservedWordsMappings().get(name);
|
||||
}
|
||||
return "_" + name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Location to write api files. You can use the apiPackage() as defined when the class is
|
||||
* instantiated
|
||||
*/
|
||||
@Override
|
||||
public String apiFileFolder() {
|
||||
return outputFolder + File.separator + apiPackage().replace('.', File.separatorChar);
|
||||
}
|
||||
|
||||
public String getExportedName() {
|
||||
return exportedName;
|
||||
}
|
||||
|
||||
public void setExportedName(String name) {
|
||||
exportedName = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> postProcessOperationsWithModels(Map<String, Object> objs, List<Object> allModels) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> objectMap = (Map<String, Object>) objs.get("operations");
|
||||
@SuppressWarnings("unchecked")
|
||||
List<CodegenOperation> operations = (List<CodegenOperation>) objectMap.get("operation");
|
||||
for (CodegenOperation operation : operations) {
|
||||
operation.httpMethod = operation.httpMethod.toLowerCase(Locale.ROOT);
|
||||
|
||||
List<CodegenParameter> params = operation.allParams;
|
||||
if (params != null && params.size() == 0) {
|
||||
operation.allParams = null;
|
||||
}
|
||||
List<CodegenResponse> responses = operation.responses;
|
||||
if (responses != null) {
|
||||
for (CodegenResponse resp : responses) {
|
||||
if ("0".equals(resp.code)) {
|
||||
resp.code = "default";
|
||||
}
|
||||
}
|
||||
}
|
||||
if (operation.examples != null && !operation.examples.isEmpty()) {
|
||||
// Leave application/json* items only
|
||||
for (Iterator<Map<String, String>> it = operation.examples.iterator(); it.hasNext(); ) {
|
||||
final Map<String, String> example = it.next();
|
||||
final String contentType = example.get("contentType");
|
||||
if (contentType == null || !contentType.startsWith("application/json")) {
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return objs;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static List<Map<String, Object>> getOperations(Map<String, Object> objs) {
|
||||
List<Map<String, Object>> result = new ArrayList<Map<String, Object>>();
|
||||
Map<String, Object> apiInfo = (Map<String, Object>) objs.get("apiInfo");
|
||||
List<Map<String, Object>> apis = (List<Map<String, Object>>) apiInfo.get("apis");
|
||||
for (Map<String, Object> api : apis) {
|
||||
result.add((Map<String, Object>) api.get("operations"));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static List<Map<String, Object>> sortOperationsByPath(List<CodegenOperation> ops) {
|
||||
Multimap<String, CodegenOperation> opsByPath = ArrayListMultimap.create();
|
||||
|
||||
for (CodegenOperation op : ops) {
|
||||
opsByPath.put(op.path, op);
|
||||
}
|
||||
|
||||
List<Map<String, Object>> opsByPathList = new ArrayList<Map<String, Object>>();
|
||||
for (Entry<String, Collection<CodegenOperation>> entry : opsByPath.asMap().entrySet()) {
|
||||
Map<String, Object> opsByPathEntry = new HashMap<String, Object>();
|
||||
opsByPathList.add(opsByPathEntry);
|
||||
opsByPathEntry.put("path", entry.getKey());
|
||||
opsByPathEntry.put("operation", entry.getValue());
|
||||
List<CodegenOperation> operationsForThisPath = Lists.newArrayList(entry.getValue());
|
||||
operationsForThisPath.get(operationsForThisPath.size() - 1).hasMore = false;
|
||||
if (opsByPathList.size() < opsByPath.asMap().size()) {
|
||||
opsByPathEntry.put("hasMore", "true");
|
||||
}
|
||||
}
|
||||
|
||||
return opsByPathList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processOpts() {
|
||||
super.processOpts();
|
||||
|
||||
if (additionalProperties.containsKey(EXPORTED_NAME)) {
|
||||
setExportedName((String) additionalProperties.get(EXPORTED_NAME));
|
||||
}
|
||||
|
||||
/*
|
||||
* 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("controller.mustache",
|
||||
// "controllers",
|
||||
// "controller.js")
|
||||
// );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preprocessOpenAPI(OpenAPI openAPI) {
|
||||
URL url = URLPathUtils.getServerURL(openAPI);
|
||||
String host = URLPathUtils.getProtocolAndHost(url);
|
||||
String port = URLPathUtils.getPort(url, defaultServerPort) ;
|
||||
String basePath = url.getPath();
|
||||
|
||||
if (additionalProperties.containsKey(SERVER_PORT)) {
|
||||
port = additionalProperties.get(SERVER_PORT).toString();
|
||||
}
|
||||
this.additionalProperties.put(SERVER_PORT, port);
|
||||
|
||||
if (openAPI.getInfo() != null) {
|
||||
Info info = openAPI.getInfo();
|
||||
if (info.getTitle() != null) {
|
||||
// when info.title is defined, use it for projectName
|
||||
// used in package.json
|
||||
projectName = info.getTitle()
|
||||
.replaceAll("[^a-zA-Z0-9]", "-")
|
||||
.replaceAll("^[-]*", "")
|
||||
.replaceAll("[-]*$", "")
|
||||
.replaceAll("[-]{2,}", "-")
|
||||
.toLowerCase(Locale.ROOT);
|
||||
this.additionalProperties.put("projectName", projectName);
|
||||
}
|
||||
}
|
||||
|
||||
// need vendor extensions
|
||||
Paths paths = openAPI.getPaths();
|
||||
if (paths != null) {
|
||||
for (String pathname : paths.keySet()) {
|
||||
PathItem path = paths.get(pathname);
|
||||
Map<HttpMethod, Operation> operationMap = path.readOperationsMap();
|
||||
if (operationMap != null) {
|
||||
for (HttpMethod method : operationMap.keySet()) {
|
||||
Operation operation = operationMap.get(method);
|
||||
String tag = "default";
|
||||
if (operation.getTags() != null && operation.getTags().size() > 0) {
|
||||
tag = toApiName(operation.getTags().get(0));
|
||||
}
|
||||
if (operation.getOperationId() == null) {
|
||||
operation.setOperationId(getOrGenerateOperationId(operation, pathname, method.toString()));
|
||||
}
|
||||
// add x-openapi-router-controller
|
||||
if (operation.getExtensions() == null ||
|
||||
operation.getExtensions().get("x-openapi-router-controller") == null) {
|
||||
operation.addExtension("x-openapi-router-controller", sanitizeTag(tag) + "Controller");
|
||||
}
|
||||
// add x-openapi-router-service
|
||||
if (operation.getExtensions() == null ||
|
||||
operation.getExtensions().get("x-openapi-router-service") == null) {
|
||||
operation.addExtension("x-openapi-router-service", sanitizeTag(tag) + "Service");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> postProcessSupportingFileData(Map<String, Object> objs) {
|
||||
generateYAMLSpecFile(objs);
|
||||
|
||||
for (Map<String, Object> operations : getOperations(objs)) {
|
||||
@SuppressWarnings("unchecked")
|
||||
List<CodegenOperation> ops = (List<CodegenOperation>) operations.get("operation");
|
||||
|
||||
List<Map<String, Object>> opsByPathList = sortOperationsByPath(ops);
|
||||
operations.put("operationsByPath", opsByPathList);
|
||||
}
|
||||
return super.postProcessSupportingFileData(objs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String removeNonNameElementToCamelCase(String name) {
|
||||
return removeNonNameElementToCamelCase(name, "[-:;#]");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String escapeUnsafeCharacters(String input) {
|
||||
return input.replace("*/", "*_/").replace("/*", "/_*");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String escapeQuotationMark(String input) {
|
||||
// remove " to avoid code injection
|
||||
return input.replace("\"", "");
|
||||
}
|
||||
}
|
@ -61,6 +61,7 @@ org.openapitools.codegen.languages.JMeterClientCodegen
|
||||
org.openapitools.codegen.languages.LuaClientCodegen
|
||||
org.openapitools.codegen.languages.MysqlSchemaCodegen
|
||||
org.openapitools.codegen.languages.NodeJSServerCodegen
|
||||
org.openapitools.codegen.languages.NodeJSExpressServerCodegen
|
||||
org.openapitools.codegen.languages.ObjcClientCodegen
|
||||
org.openapitools.codegen.languages.OCamlClientCodegen
|
||||
org.openapitools.codegen.languages.OpenAPIGenerator
|
||||
|
72
modules/openapi-generator/src/main/resources/nodejs-express-server/README.mustache
vendored
Normal file
72
modules/openapi-generator/src/main/resources/nodejs-express-server/README.mustache
vendored
Normal file
@ -0,0 +1,72 @@
|
||||
{{=<% %>=}}
|
||||
# OpenAPI Generated JavaScript/Express Server
|
||||
|
||||
## Overview
|
||||
This server was generated using the [OpenAPI Generator](https://openapi-generator.tech) project. The code generator, and it's generated code allows you to develop your system with an API-First attitude, where the API contract is the anchor and definer of your project, and your code and business-logic aims to complete and comply to the terms in the API contract.
|
||||
|
||||
### prerequisites
|
||||
- NodeJS >= 10.4
|
||||
- NPM >= 6.10.0
|
||||
|
||||
The code was written on a mac, so assuming all should work smoothly on Linux-based computers. However, there is no reason not to run this library on Windows-based machines. If you find an OS-related problem, please open an issue and it will be resolved.
|
||||
|
||||
### Running the server
|
||||
To run the server, run:
|
||||
|
||||
```
|
||||
npm start
|
||||
```
|
||||
### View and test the API
|
||||
You can see the API documentation, and check the available endpoints by going to http://localhost:3000/api-docs/. Endpoints that require security need to have security handlers configured before they can return a successful response. At this point they will return [ a response code of 401](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401).
|
||||
##### At this stage the server does not support document body sent in xml format. Forms will be supported in the near future.
|
||||
|
||||
### Node version and guidelines
|
||||
The code was written using Node version 10.6, and complies to the [Airbnb .eslint guiding rules](https://github.com/airbnb/javascript).
|
||||
|
||||
### Project Files
|
||||
#### Root Directory:
|
||||
In the root directory we have (besides package.json, config.js, and log files):
|
||||
- **logger.js** - where we define the logger for the project. The project uses winston, but the purpose of this file is to enable users to change and modify their own logger behavior.
|
||||
- **index.js** - This is the project's 'main' file, and from here we launch the application. This is a very short and concise file, and the idea behind launching from this short file is to allow use-cases of launching the server with different parameters (changing config and/or logger) without affecting the rest of the code.
|
||||
- **expressServer.js** - The core of the Express.js server. This is where the express server is initialized, together with the OpenAPI validator, OpenAPI UI, and other libraries needed to start our server. If we want to add external links, that's where they would go. Our project uses the [express-openapi-validator](https://www.npmjs.com/package/express-openapi-validator) library that acts as a first step in the routing process - requests that are directed to paths defined in the `openapi.yaml` file are caught by this process, and it's parameters and bodyContent are validated against the schema. A successful result of this validation will be a new 'openapi' object added to the request. If the path requested is not part of the openapi.yaml file, the validator ignores the request and passes it on, as is, down the flow of the Express server.
|
||||
|
||||
#### api/
|
||||
- **openapi.yaml** - This is the OpenAPI contract to which this server will comply. The file was generated using the codegen, and should contain everything needed to run the API Gateway - no references to external models/schemas.
|
||||
|
||||
#### utils/
|
||||
Currently a single file:
|
||||
|
||||
- **openapiRouter.js** - This is where the routing to our back-end code happens. If the request object includes an ```openapi``` object, it picks up the following values (that are part of the ```openapi.yaml``` file): 'x-openapi-router-controller', and 'x-openapi-router-service'. These variables are names of files/classes in the controllers and services directories respectively. The operationId of the request is also extracted. The operationId is a method in the controller and the service that was generated as part of the codegen process. The routing process sends the request and response objects to the controller, which will extract the expected variables from the request, and send it to be processed by the service, returning the response from the service to the caller.
|
||||
|
||||
#### controllers/
|
||||
After validating the request, and ensuring this belongs to our API gateway, we send the request to a `controller`, where the variables and parameters are extracted from the request and sent to the relevant `service` for processing. The `controller` handles the response from the `service` and builds the appropriate HTTP response to be sent back to the user.
|
||||
|
||||
- **index.js** - load all the controllers that were generated for this project, and export them to be used dynamically by the `openapiRouter.js`. If you would like to customize your controller, it is advised that you link to your controller here, and ensure that the codegen does not rewrite this file.
|
||||
|
||||
- **Controller.js** - The core processor of the generated controllers. The generated controllers are designed to be as slim and generic as possible, referencing to the `Controller.js` for the business logic of parsing the needed variables and arguments from the request, and for building the HTTP response which will be sent back. The `Controller.js` is a class with static methods.
|
||||
|
||||
- **{{x-openapi-router-controller}}.js** - auto-generated code, processing all the operations. The Controller is a class that is constructed with the service class it will be sending the request to. Every request defined by the `openapi.yaml` has an operationId. The operationId is the name of the method that will be called. Every method receives the request and response, and calls the `Controller.js` to process the request and response, adding the service method that should be called for the actual business-logic processing.
|
||||
|
||||
#### services/
|
||||
This is where the API Gateway ends, and the unique business-logic of your application kicks in. Every endpoint in the `openapi.yaml` has a variable 'x-openapi-router-service', which is the name of the service class that is generated. The operationID of the endpoint is the name of the method that will be called. The generated code provides a simple promise with a try/catch clause. A successful operation ends with a call to the generic `Service.js` to build a successful response (payload and response code), and a failure will call the generic `Service.js` to build a response with an error object and the relevant response code. It is recommended to have the services be generated automatically once, and after the initial build add methods manually.
|
||||
|
||||
- **index.js** - load all the services that were generated for this project, and export them to be used dynamically by the `openapiRouter.js`. If you would like to customize your service, it is advised that you link to your controller here, and ensure that the codegen does not rewrite this file.
|
||||
|
||||
- **Service.js** - A utility class, very simple and thin at this point, with two static methods for building a response object for successful and failed results in the service operation. The default response code is 200 for success and 500 for failure. It is recommended to send more accurate response codes and override these defaults when relevant.
|
||||
|
||||
- **{{x-openapi-router-service}}.js** - auto-generated code, providing a stub Promise for each operationId defined in the `openapi.yaml`. Each method receives the variables that were defined in the `openapi.yaml` file, and wraps a Promise in a try/catch clause. The Promise resolves both success and failure in a call to the `Service.js` utility class for building the appropriate response that will be sent back to the Controller and then to the caller of this endpoint.
|
||||
|
||||
#### tests/
|
||||
- **serverTests.js** - basic server validation tests, checking that the server is up, that a call to an endpoint within the scope of the `openapi.yaml` file returns 200, that a call to a path outside that scope returns 200 if it exists and a 404 if not.
|
||||
- **routingTests.js** - Runs through all the endpoints defined in the `openapi.yaml`, and constructs a dummy request to send to the server. Confirms that the response code is 200. At this point requests containing xml or formData fail - currently they are not supported in the router.
|
||||
- **additionalEndpointsTests.js** - A test file for all the endpoints that are defined outside the openapi.yaml scope. Confirms that these endpoints return a successful 200 response.
|
||||
|
||||
|
||||
Future tests should be written to ensure that the response of every request sent should conform to the structure defined in the `openapi.yaml`. This test will fail 100% initially, and the job of the development team will be to clear these tests.
|
||||
|
||||
|
||||
#### models/
|
||||
Currently a concept awaiting feedback. The idea is to have the objects defined in the openapi.yaml act as models which are passed between the different modules. This will conform the programmers to interact using defined objects, rather than loosley-defined JSON objects. Given the nature of JavaScript progrmmers, who want to work with their own bootstrapped parameters, this concept might not work. Keeping this here for future discussion and feedback.
|
||||
|
||||
|
||||
|
30
modules/openapi-generator/src/main/resources/nodejs-express-server/app.mustache
vendored
Normal file
30
modules/openapi-generator/src/main/resources/nodejs-express-server/app.mustache
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
const ExpressServer = require('./expressServer');
|
||||
const logger = require('./logger');
|
||||
// const swaggerRouter = require('./utils/swaggerRouter');
|
||||
|
||||
class App {
|
||||
constructor(config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
async launch() {
|
||||
try {
|
||||
this.expressServer = new ExpressServer(this.config.URL_PORT, this.config.OPENAPI_YAML);
|
||||
// this.expressServer.app.use(swaggerRouter());
|
||||
await this.expressServer.launch();
|
||||
logger.info('Express server running');
|
||||
} catch (error) {
|
||||
logger.error(error);
|
||||
await this.close();
|
||||
}
|
||||
}
|
||||
|
||||
async close() {
|
||||
if (this.expressServer !== undefined) {
|
||||
await this.expressServer.close();
|
||||
logger.info(`Server shut down on port ${this.config.URL_PORT}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = App;
|
12
modules/openapi-generator/src/main/resources/nodejs-express-server/config.mustache
vendored
Normal file
12
modules/openapi-generator/src/main/resources/nodejs-express-server/config.mustache
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
const path = require('path');
|
||||
|
||||
const config = {
|
||||
ROOT_DIR: __dirname,
|
||||
URL_PORT: 3000,
|
||||
URL_PATH: 'http://localhost',
|
||||
BASE_VERSION: 'v2',
|
||||
CONTROLLER_DIRECTORY: path.join(__dirname, 'controllers'),
|
||||
};
|
||||
config.OPENAPI_YAML = path.join(config.ROOT_DIR, 'api', 'openapi.yaml');
|
||||
config.FULL_PATH = `${config.URL_PATH}:${config.URL_PORT}/${config.BASE_VERSION}`;
|
||||
module.exports = config;
|
18
modules/openapi-generator/src/main/resources/nodejs-express-server/controller.mustache
vendored
Normal file
18
modules/openapi-generator/src/main/resources/nodejs-express-server/controller.mustache
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
const Controller = require('./Controller');
|
||||
|
||||
class {{{classname}}}Controller {
|
||||
constructor(Service) {
|
||||
this.service = Service;
|
||||
}
|
||||
|
||||
{{#operations}}
|
||||
{{#operation}}
|
||||
async {{operationId}}(request, response) {
|
||||
await Controller.handleRequest(request, response, this.service.{{operationId}});
|
||||
}
|
||||
|
||||
{{/operation}}
|
||||
}
|
||||
|
||||
module.exports = {{classname}}Controller;
|
||||
{{/operations}}
|
@ -0,0 +1,72 @@
|
||||
const logger = require('../logger');
|
||||
|
||||
class Controller {
|
||||
static sendResponse(response, payload) {
|
||||
/**
|
||||
* The default response-code is 200. We want to allow to change that. in That case,
|
||||
* payload will be an object consisting of a code and a payload. If not customized
|
||||
* send 200 and the payload as received in this method.
|
||||
*/
|
||||
response.status(payload.code || 200);
|
||||
const responsePayload = payload.payload !== undefined ? payload.payload : payload;
|
||||
if (responsePayload instanceof Object) {
|
||||
response.json(responsePayload);
|
||||
} else {
|
||||
response.end(responsePayload);
|
||||
}
|
||||
}
|
||||
|
||||
static sendError(response, error) {
|
||||
response.status(error.code || 500);
|
||||
if (error.error instanceof Object) {
|
||||
response.json(error.error);
|
||||
} else {
|
||||
response.end(error.error || error.message);
|
||||
}
|
||||
}
|
||||
|
||||
static collectFiles(request) {
|
||||
logger.info('Checking if files are expected in schema');
|
||||
if (request.openapi.schema.requestBody !== undefined) {
|
||||
const [contentType] = request.headers['content-type'].split(';');
|
||||
if (contentType === 'multipart/form-data') {
|
||||
const contentSchema = request.openapi.schema.requestBody.content[contentType].schema;
|
||||
Object.entries(contentSchema.properties).forEach(([name, property]) => {
|
||||
if (property.type === 'string' && ['binary', 'base64'].indexOf(property.format) > -1) {
|
||||
request.body[name] = request.files.find(file => file.fieldname === name);
|
||||
}
|
||||
});
|
||||
} else if (request.openapi.schema.requestBody.content[contentType] !== undefined
|
||||
&& request.files !== undefined) {
|
||||
[request.body] = request.files;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static collectRequestParams(request) {
|
||||
this.collectFiles(request);
|
||||
const requestParams = {};
|
||||
if (request.openapi.schema.requestBody !== undefined) {
|
||||
requestParams.body = request.body;
|
||||
}
|
||||
request.openapi.schema.parameters.forEach((param) => {
|
||||
if (param.in === 'path') {
|
||||
requestParams[param.name] = request.openapi.pathParams[param.name];
|
||||
} else if (param.in === 'query') {
|
||||
requestParams[param.name] = request.query[param.name];
|
||||
}
|
||||
});
|
||||
return requestParams;
|
||||
}
|
||||
|
||||
static async handleRequest(request, response, serviceOperation) {
|
||||
try {
|
||||
const serviceResponse = await serviceOperation(this.collectRequestParams(request));
|
||||
Controller.sendResponse(response, serviceResponse);
|
||||
} catch (error) {
|
||||
Controller.sendError(response, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Controller;
|
25
modules/openapi-generator/src/main/resources/nodejs-express-server/controllers/index.mustache
vendored
Normal file
25
modules/openapi-generator/src/main/resources/nodejs-express-server/controllers/index.mustache
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
{{#apiInfo}}
|
||||
{{#apis}}
|
||||
{{#operations}}
|
||||
{{#operation}}
|
||||
{{#-first}}
|
||||
const {{classname}}Controller = require('./{{classname}}Controller');
|
||||
{{/-first}}
|
||||
{{/operation}}
|
||||
{{/operations}}
|
||||
{{/apis}}
|
||||
{{/apiInfo}}
|
||||
|
||||
module.exports = {
|
||||
{{#apiInfo}}
|
||||
{{#apis}}
|
||||
{{#operations}}
|
||||
{{#operation}}
|
||||
{{#-first}}
|
||||
{{classname}}Controller,
|
||||
{{/-first}}
|
||||
{{/operation}}
|
||||
{{/operations}}
|
||||
{{/apis}}
|
||||
{{/apiInfo}}
|
||||
};
|
72
modules/openapi-generator/src/main/resources/nodejs-express-server/controllers/test.mustache
vendored
Normal file
72
modules/openapi-generator/src/main/resources/nodejs-express-server/controllers/test.mustache
vendored
Normal file
@ -0,0 +1,72 @@
|
||||
const Service = require('../services/Service');
|
||||
|
||||
const testItems = require('../tests/testFiles/testItems.json');
|
||||
|
||||
class TestService {
|
||||
static testGetController() {
|
||||
return new Promise(
|
||||
async (resolve, reject) => {
|
||||
try {
|
||||
resolve(Service.successResponse(
|
||||
testItems,
|
||||
200,
|
||||
));
|
||||
} catch (e) {
|
||||
const message = e.getMessage() || 'Could not get items. Server error';
|
||||
reject(Service.rejectResponse(message, 500));
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
sendResponse(request, response) {
|
||||
response.status(200);
|
||||
const objectToReturn = {};
|
||||
Object.keys(request.swagger.paramValues).forEach((key) => {
|
||||
const val = request.swagger.paramValues[key];
|
||||
if (val instanceof Object) {
|
||||
objectToReturn[key] = val.originalname || val.name || val;
|
||||
} else {
|
||||
objectToReturn[key] = request.swagger.paramValues[key];
|
||||
}
|
||||
});
|
||||
response.json(objectToReturn);
|
||||
}
|
||||
|
||||
confirmRouteGetSingle(request, response) {
|
||||
this.sendResponse(request, response);
|
||||
}
|
||||
|
||||
confirmRouteGetMany(request, response) {
|
||||
this.sendResponse(request, response);
|
||||
}
|
||||
|
||||
confirmRoutePost(request, response) {
|
||||
this.sendResponse(request, response);
|
||||
}
|
||||
|
||||
confirmRoutePut(request, response) {
|
||||
this.sendResponse(request, response);
|
||||
}
|
||||
|
||||
async testGetController(request, response) {
|
||||
await Controller.handleRequest(request, response, this.service.testGetController);
|
||||
}
|
||||
|
||||
async testPostController(request, response) {
|
||||
await Controller.handleRequest(request, response, this.service.testPostController);
|
||||
}
|
||||
|
||||
async testPutController(request, response) {
|
||||
await Controller.handleRequest(request, response, this.service.testPutController);
|
||||
}
|
||||
|
||||
async testDeleteController(request, response) {
|
||||
await Controller.handleRequest(request, response, this.service.testDeleteController);
|
||||
}
|
||||
|
||||
async testFindByIdController(request, response) {
|
||||
await Controller.handleRequest(request, response, this.service.testFindByIdController);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = TestController;
|
8
modules/openapi-generator/src/main/resources/nodejs-express-server/eslintrc.mustache
vendored
Normal file
8
modules/openapi-generator/src/main/resources/nodejs-express-server/eslintrc.mustache
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
// Use this file as a starting point for your project's .eslintrc.
|
||||
// Copy this file, and add rule overrides as needed.
|
||||
{
|
||||
"extends": "airbnb",
|
||||
"rules": {
|
||||
"no-console": "off"
|
||||
}
|
||||
}
|
93
modules/openapi-generator/src/main/resources/nodejs-express-server/expressServer.mustache
vendored
Normal file
93
modules/openapi-generator/src/main/resources/nodejs-express-server/expressServer.mustache
vendored
Normal file
@ -0,0 +1,93 @@
|
||||
// const { Middleware } = require('swagger-express-middleware');
|
||||
const path = require('path');
|
||||
const swaggerUI = require('swagger-ui-express');
|
||||
const yamljs = require('yamljs');
|
||||
const express = require('express');
|
||||
const cors = require('cors');
|
||||
const cookieParser = require('cookie-parser');
|
||||
const bodyParser = require('body-parser');
|
||||
const { OpenApiValidator } = require('express-openapi-validator');
|
||||
const openapiRouter = require('./utils/openapiRouter');
|
||||
const logger = require('./logger');
|
||||
|
||||
class ExpressServer {
|
||||
constructor(port, openApiYaml) {
|
||||
this.port = port;
|
||||
this.app = express();
|
||||
this.openApiPath = openApiYaml;
|
||||
this.schema = yamljs.load(openApiYaml);
|
||||
this.setupMiddleware();
|
||||
}
|
||||
|
||||
setupMiddleware() {
|
||||
// this.setupAllowedMedia();
|
||||
this.app.use(cors());
|
||||
this.app.use(bodyParser.json());
|
||||
this.app.use(express.json());
|
||||
this.app.use(express.urlencoded({ extended: false }));
|
||||
this.app.use(cookieParser());
|
||||
this.app.use('/spec', express.static(path.join(__dirname, 'api')));
|
||||
this.app.get('/hello', (req, res) => res.send('Hello World. path: '+this.openApiPath));
|
||||
// this.app.get('/spec', express.static(this.openApiPath));
|
||||
this.app.use('/api-docs', swaggerUI.serve, swaggerUI.setup(this.schema));
|
||||
this.app.get('/login-redirect', (req, res) => {
|
||||
res.status(200);
|
||||
res.json(req.query);
|
||||
});
|
||||
this.app.get('/oauth2-redirect.html', (req, res) => {
|
||||
res.status(200);
|
||||
res.json(req.query);
|
||||
});
|
||||
new OpenApiValidator({
|
||||
apiSpecPath: this.openApiPath,
|
||||
}).install(this.app);
|
||||
this.app.use(openapiRouter());
|
||||
this.app.get('/', (req, res) => {
|
||||
res.status(200);
|
||||
res.end('Hello World');
|
||||
});
|
||||
}
|
||||
|
||||
addErrorHandler() {
|
||||
this.app.use('*', (req, res) => {
|
||||
res.status(404);
|
||||
res.send(JSON.stringify({ error: `path ${req.baseUrl} doesn't exist` }));
|
||||
});
|
||||
/**
|
||||
* suppressed eslint rule: The next variable is required here, even though it's not used.
|
||||
*
|
||||
** */
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
this.app.use((error, req, res, next) => {
|
||||
const errorResponse = error.error || error.errors || error.message || 'Unknown error';
|
||||
res.status(error.status || 500);
|
||||
res.type('json');
|
||||
res.json({ error: errorResponse });
|
||||
});
|
||||
}
|
||||
|
||||
async launch() {
|
||||
return new Promise(
|
||||
async (resolve, reject) => {
|
||||
try {
|
||||
this.addErrorHandler();
|
||||
this.server = await this.app.listen(this.port, () => {
|
||||
console.log(`server running on port ${this.port}`);
|
||||
resolve(this.server);
|
||||
});
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
async close() {
|
||||
if (this.server !== undefined) {
|
||||
await this.server.close();
|
||||
console.log(`Server on port ${this.port} shut down`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ExpressServer;
|
28
modules/openapi-generator/src/main/resources/nodejs-express-server/index.mustache
vendored
Normal file
28
modules/openapi-generator/src/main/resources/nodejs-express-server/index.mustache
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
const config = require('./config');
|
||||
const logger = require('./logger');
|
||||
const ExpressServer = require('./expressServer');
|
||||
// const App = require('./app');
|
||||
|
||||
// const app = new App(config);
|
||||
// app.launch()
|
||||
// .then(() => {
|
||||
// logger.info('Server launched');
|
||||
// })
|
||||
// .catch((error) => {
|
||||
// logger.error('found error, shutting down server');
|
||||
// app.close()
|
||||
// .catch(closeError => logger.error(closeError))
|
||||
// .finally(() => logger.error(error));
|
||||
// });
|
||||
const launchServer = async () => {
|
||||
try {
|
||||
this.expressServer = new ExpressServer(config.URL_PORT, config.OPENAPI_YAML);
|
||||
await this.expressServer.launch();
|
||||
logger.info('Express server running');
|
||||
} catch (error) {
|
||||
logger.error(error);
|
||||
await this.close();
|
||||
}
|
||||
};
|
||||
|
||||
launchServer().catch(e => logger.error(e));
|
17
modules/openapi-generator/src/main/resources/nodejs-express-server/logger.mustache
vendored
Normal file
17
modules/openapi-generator/src/main/resources/nodejs-express-server/logger.mustache
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
const winston = require('winston');
|
||||
|
||||
const logger = winston.createLogger({
|
||||
level: 'info',
|
||||
format: winston.format.json(),
|
||||
defaultMeta: { service: 'user-service' },
|
||||
transports: [
|
||||
new winston.transports.File({ filename: 'error.log', level: 'error' }),
|
||||
new winston.transports.File({ filename: 'combined.log' }),
|
||||
],
|
||||
});
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
logger.add(new winston.transports.Console({ format: winston.format.simple() }));
|
||||
}
|
||||
|
||||
module.exports = logger;
|
1
modules/openapi-generator/src/main/resources/nodejs-express-server/openapi.mustache
vendored
Normal file
1
modules/openapi-generator/src/main/resources/nodejs-express-server/openapi.mustache
vendored
Normal file
@ -0,0 +1 @@
|
||||
{{{openapi-yaml}}}
|
46
modules/openapi-generator/src/main/resources/nodejs-express-server/package.mustache
vendored
Normal file
46
modules/openapi-generator/src/main/resources/nodejs-express-server/package.mustache
vendored
Normal file
@ -0,0 +1,46 @@
|
||||
{
|
||||
"name": "openapi-petstore",
|
||||
"version": "1.0.0",
|
||||
"description": "This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters.",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"prestart": "npm install",
|
||||
"start": "node index.js"
|
||||
},
|
||||
"keywords": [
|
||||
"openapi-generator",
|
||||
"openapi"
|
||||
],
|
||||
"license": "Unlicense",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"body-parser": "^1.19.0",
|
||||
"connect": "^3.2.0",
|
||||
"cookie-parser": "^1.4.4",
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.16.4",
|
||||
"express-openapi-validator": "^1.0.0",
|
||||
"js-yaml": "^3.3.0",
|
||||
"jstoxml": "^1.5.0",
|
||||
"ono": "^5.0.1",
|
||||
"openapi-sampler": "^1.0.0-beta.15",
|
||||
"swagger-express-middleware": "^2.0.2",
|
||||
"swagger-tools": "^0.10.4",
|
||||
"swagger-ui-express": "^4.0.2",
|
||||
"winston": "^3.2.1",
|
||||
"yamljs": "^0.3.0",
|
||||
"mocha": "^6.1.4",
|
||||
"axios": "^0.19.0",
|
||||
"chai": "^4.2.0",
|
||||
"chai-as-promised": "^7.1.1",
|
||||
"eslint": "^5.16.0",
|
||||
"eslint-config-airbnb-base": "^13.1.0",
|
||||
"eslint-plugin-import": "^2.17.2",
|
||||
"form-data": "^2.3.3"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"env": {
|
||||
"node": true
|
||||
}
|
||||
}
|
||||
}
|
45
modules/openapi-generator/src/main/resources/nodejs-express-server/service.mustache
vendored
Normal file
45
modules/openapi-generator/src/main/resources/nodejs-express-server/service.mustache
vendored
Normal file
@ -0,0 +1,45 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
const Service = require('./Service');
|
||||
|
||||
class {{{classname}}}Service {
|
||||
|
||||
{{#operations}}
|
||||
{{#operation}}
|
||||
/**
|
||||
{{#summary}}
|
||||
* {{{summary}}}
|
||||
{{/summary}}
|
||||
{{#notes}}
|
||||
* {{{notes}}}
|
||||
{{/notes}}
|
||||
*
|
||||
{{#allParams}}
|
||||
* {{paramName}} {{{dataType}}} {{{description}}}{{^required}} (optional){{/required}}
|
||||
{{/allParams}}
|
||||
{{^returnType}}
|
||||
* no response value expected for this operation
|
||||
{{/returnType}}
|
||||
{{#returnType}}
|
||||
* returns {{{returnType}}}
|
||||
{{/returnType}}
|
||||
**/
|
||||
static {{{operationId}}}({{#allParams}}{{#-first}}{ {{/-first}}{{paramName}}{{#hasMore}}, {{/hasMore}}{{#-last}} }{{/-last}}{{/allParams}}) {
|
||||
return new Promise(
|
||||
async (resolve) => {
|
||||
try {
|
||||
resolve(Service.successResponse(''));
|
||||
} catch (e) {
|
||||
resolve(Service.rejectResponse(
|
||||
e.message || 'Invalid input',
|
||||
e.status || 405,
|
||||
));
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
{{/operation}}
|
||||
}
|
||||
|
||||
module.exports = {{{classname}}}Service;
|
||||
{{/operations}}
|
11
modules/openapi-generator/src/main/resources/nodejs-express-server/services/Service.mustache
vendored
Normal file
11
modules/openapi-generator/src/main/resources/nodejs-express-server/services/Service.mustache
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
class Service {
|
||||
static rejectResponse(error, code = 500) {
|
||||
return { error, code };
|
||||
}
|
||||
|
||||
static successResponse(payload, code = 200) {
|
||||
return { payload, code };
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Service;
|
25
modules/openapi-generator/src/main/resources/nodejs-express-server/services/index.mustache
vendored
Normal file
25
modules/openapi-generator/src/main/resources/nodejs-express-server/services/index.mustache
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
{{#apiInfo}}
|
||||
{{#apis}}
|
||||
{{#operations}}
|
||||
{{#operation}}
|
||||
{{#-first}}
|
||||
const {{classname}}Service = require('./{{classname}}Service');
|
||||
{{/-first}}
|
||||
{{/operation}}
|
||||
{{/operations}}
|
||||
{{/apis}}
|
||||
{{/apiInfo}}
|
||||
|
||||
module.exports = {
|
||||
{{#apiInfo}}
|
||||
{{#apis}}
|
||||
{{#operations}}
|
||||
{{#operation}}
|
||||
{{#-first}}
|
||||
{{classname}}Service,
|
||||
{{/-first}}
|
||||
{{/operation}}
|
||||
{{/operations}}
|
||||
{{/apis}}
|
||||
{{/apiInfo}}
|
||||
};
|
67
modules/openapi-generator/src/main/resources/nodejs-express-server/utils/openapiRouter.mustache
vendored
Normal file
67
modules/openapi-generator/src/main/resources/nodejs-express-server/utils/openapiRouter.mustache
vendored
Normal file
@ -0,0 +1,67 @@
|
||||
const logger = require('../logger');
|
||||
const controllers = require('../controllers');
|
||||
const Services = require('../services');
|
||||
|
||||
function handleError(err, request, response, next) {
|
||||
logger.error(err);
|
||||
const code = err.code || 400;
|
||||
response.status(code);
|
||||
response.error = err;
|
||||
next(JSON.stringify({
|
||||
code,
|
||||
error: err,
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* The purpose of this route is to collect the request variables as defined in the
|
||||
* OpenAPI document and pass them to the handling controller as another Express
|
||||
* middleware. All parameters are collected in the requet.swagger.values key-value object
|
||||
*
|
||||
* The assumption is that security handlers have already verified and allowed access
|
||||
* to this path. If the business-logic of a particular path is dependant on authentication
|
||||
* parameters (e.g. scope checking) - it is recommended to define the authentication header
|
||||
* as one of the parameters expected in the OpenAPI/Swagger document.
|
||||
*
|
||||
* Requests made to paths that are not in the OpernAPI scope
|
||||
* are passed on to the next middleware handler.
|
||||
* @returns {Function}
|
||||
*/
|
||||
function openApiRouter() {
|
||||
return async (request, response, next) => {
|
||||
try {
|
||||
/**
|
||||
* This middleware runs after a previous process have applied an openapi object
|
||||
* to the request.
|
||||
* If none was applied This is because the path requested is not in the schema.
|
||||
* If there's no openapi object, we have nothing to do, and pass on to next middleware.
|
||||
*/
|
||||
if (request.openapi === undefined
|
||||
|| request.openapi.schema === undefined
|
||||
) {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
// request.swagger.paramValues = {};
|
||||
// request.swagger.params.forEach((param) => {
|
||||
// request.swagger.paramValues[param.name] = getValueFromRequest(request, param);
|
||||
// });
|
||||
const controllerName = request.openapi.schema['x-openapi-router-controller'];
|
||||
const serviceName = request.openapi.schema['x-openapi-router-service'];
|
||||
if (!controllers[controllerName] || controllers[controllerName] === undefined) {
|
||||
handleError(`request sent to controller '${controllerName}' which has not been defined`,
|
||||
request, response, next);
|
||||
} else {
|
||||
const apiController = new controllers[controllerName](Services[serviceName]);
|
||||
const controllerOperation = request.openapi.schema.operationId;
|
||||
await apiController[controllerOperation](request, response, next);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
const err = { code: 500, error: error.message };
|
||||
handleError(err, request, response, next);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = openApiRouter;
|
43
modules/openapi-generator/src/main/resources/nodejs-express-server/utils/writer.mustache
vendored
Normal file
43
modules/openapi-generator/src/main/resources/nodejs-express-server/utils/writer.mustache
vendored
Normal file
@ -0,0 +1,43 @@
|
||||
var ResponsePayload = function(code, payload) {
|
||||
this.code = code;
|
||||
this.payload = payload;
|
||||
}
|
||||
|
||||
exports.respondWithCode = function(code, payload) {
|
||||
return new ResponsePayload(code, payload);
|
||||
}
|
||||
|
||||
var writeJson = exports.writeJson = function(response, arg1, arg2) {
|
||||
var code;
|
||||
var payload;
|
||||
|
||||
if(arg1 && arg1 instanceof ResponsePayload) {
|
||||
writeJson(response, arg1.payload, arg1.code);
|
||||
return;
|
||||
}
|
||||
|
||||
if(arg2 && Number.isInteger(arg2)) {
|
||||
code = arg2;
|
||||
}
|
||||
else {
|
||||
if(arg1 && Number.isInteger(arg1)) {
|
||||
code = arg1;
|
||||
}
|
||||
}
|
||||
if(code && arg1) {
|
||||
payload = arg1;
|
||||
}
|
||||
else if(arg1) {
|
||||
payload = arg1;
|
||||
}
|
||||
|
||||
if(!code) {
|
||||
// if no response code given, we default to 200
|
||||
code = 200;
|
||||
}
|
||||
if(typeof payload === 'object') {
|
||||
payload = JSON.stringify(payload, null, 2);
|
||||
}
|
||||
response.writeHead(code, {'Content-Type': 'application/json'});
|
||||
response.end(payload);
|
||||
}
|
8
samples/server/petstore/nodejs-express-server/.eslintrc
Normal file
8
samples/server/petstore/nodejs-express-server/.eslintrc
Normal file
@ -0,0 +1,8 @@
|
||||
// Use this file as a starting point for your project's .eslintrc.
|
||||
// Copy this file, and add rule overrides as needed.
|
||||
{
|
||||
"extends": "airbnb",
|
||||
"rules": {
|
||||
"no-console": "off"
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
// Use this file as a starting point for your project's .eslintrc.
|
||||
// Copy this file, and add rule overrides as needed.
|
||||
{
|
||||
"extends": "airbnb",
|
||||
"rules": {
|
||||
"no-console": "off"
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
# OpenAPI Generator Ignore
|
||||
# Generated by openapi-generator https://github.com/openapitools/openapi-generator
|
||||
|
||||
# Use this file to prevent files from being overwritten by the generator.
|
||||
# The patterns follow closely to .gitignore or .dockerignore.
|
||||
|
||||
# As an example, the C# client generator defines ApiClient.cs.
|
||||
# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line:
|
||||
#ApiClient.cs
|
||||
|
||||
# You can match any string of characters against a directory, file or extension with a single asterisk (*):
|
||||
#foo/*/qux
|
||||
# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux
|
||||
|
||||
# You can recursively match patterns against a directory, file or extension with a double asterisk (**):
|
||||
#foo/**/qux
|
||||
# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux
|
||||
|
||||
# You can also negate patterns with an exclamation (!).
|
||||
# For example, you can ignore all files in a docs folder with the file extension .md:
|
||||
#docs/*.md
|
||||
# Then explicitly reverse the ignore rule for a single file:
|
||||
#!docs/README.md
|
@ -0,0 +1 @@
|
||||
4.1.0-SNAPSHOT
|
71
samples/server/petstore/nodejs-express-server/README.md
Normal file
71
samples/server/petstore/nodejs-express-server/README.md
Normal file
@ -0,0 +1,71 @@
|
||||
# OpenAPI Generated JavaScript/Express Server
|
||||
|
||||
## Overview
|
||||
This server was generated using the [OpenAPI Generator](https://openapi-generator.tech) project. The code generator, and it's generated code allows you to develop your system with an API-First attitude, where the API contract is the anchor and definer of your project, and your code and business-logic aims to complete and comply to the terms in the API contract.
|
||||
|
||||
### prerequisites
|
||||
- NodeJS >= 10.4
|
||||
- NPM >= 6.10.0
|
||||
|
||||
The code was written on a mac, so assuming all should work smoothly on Linux-based computers. However, there is no reason not to run this library on Windows-based machines. If you find an OS-related problem, please open an issue and it will be resolved.
|
||||
|
||||
### Running the server
|
||||
To run the server, run:
|
||||
|
||||
```
|
||||
npm start
|
||||
```
|
||||
### View and test the API
|
||||
You can see the API documentation, and check the available endpoints by going to http://localhost:3000/api-docs/. Endpoints that require security need to have security handlers configured before they can return a successful response. At this point they will return [ a response code of 401](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401).
|
||||
##### At this stage the server does not support document body sent in xml format. Forms will be supported in the near future.
|
||||
|
||||
### Node version and guidelines
|
||||
The code was written using Node version 10.6, and complies to the [Airbnb .eslint guiding rules](https://github.com/airbnb/javascript).
|
||||
|
||||
### Project Files
|
||||
#### Root Directory:
|
||||
In the root directory we have (besides package.json, config.js, and log files):
|
||||
- **logger.js** - where we define the logger for the project. The project uses winston, but the purpose of this file is to enable users to change and modify their own logger behavior.
|
||||
- **index.js** - This is the project's 'main' file, and from here we launch the application. This is a very short and concise file, and the idea behind launching from this short file is to allow use-cases of launching the server with different parameters (changing config and/or logger) without affecting the rest of the code.
|
||||
- **expressServer.js** - The core of the Express.js server. This is where the express server is initialized, together with the OpenAPI validator, OpenAPI UI, and other libraries needed to start our server. If we want to add external links, that's where they would go. Our project uses the [express-openapi-validator](https://www.npmjs.com/package/express-openapi-validator) library that acts as a first step in the routing process - requests that are directed to paths defined in the `openapi.yaml` file are caught by this process, and it's parameters and bodyContent are validated against the schema. A successful result of this validation will be a new 'openapi' object added to the request. If the path requested is not part of the openapi.yaml file, the validator ignores the request and passes it on, as is, down the flow of the Express server.
|
||||
|
||||
#### api/
|
||||
- **openapi.yaml** - This is the OpenAPI contract to which this server will comply. The file was generated using the codegen, and should contain everything needed to run the API Gateway - no references to external models/schemas.
|
||||
|
||||
#### utils/
|
||||
Currently a single file:
|
||||
|
||||
- **openapiRouter.js** - This is where the routing to our back-end code happens. If the request object includes an ```openapi``` object, it picks up the following values (that are part of the ```openapi.yaml``` file): 'x-openapi-router-controller', and 'x-openapi-router-service'. These variables are names of files/classes in the controllers and services directories respectively. The operationId of the request is also extracted. The operationId is a method in the controller and the service that was generated as part of the codegen process. The routing process sends the request and response objects to the controller, which will extract the expected variables from the request, and send it to be processed by the service, returning the response from the service to the caller.
|
||||
|
||||
#### controllers/
|
||||
After validating the request, and ensuring this belongs to our API gateway, we send the request to a `controller`, where the variables and parameters are extracted from the request and sent to the relevant `service` for processing. The `controller` handles the response from the `service` and builds the appropriate HTTP response to be sent back to the user.
|
||||
|
||||
- **index.js** - load all the controllers that were generated for this project, and export them to be used dynamically by the `openapiRouter.js`. If you would like to customize your controller, it is advised that you link to your controller here, and ensure that the codegen does not rewrite this file.
|
||||
|
||||
- **Controller.js** - The core processor of the generated controllers. The generated controllers are designed to be as slim and generic as possible, referencing to the `Controller.js` for the business logic of parsing the needed variables and arguments from the request, and for building the HTTP response which will be sent back. The `Controller.js` is a class with static methods.
|
||||
|
||||
- **{{x-openapi-router-controller}}.js** - auto-generated code, processing all the operations. The Controller is a class that is constructed with the service class it will be sending the request to. Every request defined by the `openapi.yaml` has an operationId. The operationId is the name of the method that will be called. Every method receives the request and response, and calls the `Controller.js` to process the request and response, adding the service method that should be called for the actual business-logic processing.
|
||||
|
||||
#### services/
|
||||
This is where the API Gateway ends, and the unique business-logic of your application kicks in. Every endpoint in the `openapi.yaml` has a variable 'x-openapi-router-service', which is the name of the service class that is generated. The operationID of the endpoint is the name of the method that will be called. The generated code provides a simple promise with a try/catch clause. A successful operation ends with a call to the generic `Service.js` to build a successful response (payload and response code), and a failure will call the generic `Service.js` to build a response with an error object and the relevant response code. It is recommended to have the services be generated automatically once, and after the initial build add methods manually.
|
||||
|
||||
- **index.js** - load all the services that were generated for this project, and export them to be used dynamically by the `openapiRouter.js`. If you would like to customize your service, it is advised that you link to your controller here, and ensure that the codegen does not rewrite this file.
|
||||
|
||||
- **Service.js** - A utility class, very simple and thin at this point, with two static methods for building a response object for successful and failed results in the service operation. The default response code is 200 for success and 500 for failure. It is recommended to send more accurate response codes and override these defaults when relevant.
|
||||
|
||||
- **{{x-openapi-router-service}}.js** - auto-generated code, providing a stub Promise for each operationId defined in the `openapi.yaml`. Each method receives the variables that were defined in the `openapi.yaml` file, and wraps a Promise in a try/catch clause. The Promise resolves both success and failure in a call to the `Service.js` utility class for building the appropriate response that will be sent back to the Controller and then to the caller of this endpoint.
|
||||
|
||||
#### tests/
|
||||
- **serverTests.js** - basic server validation tests, checking that the server is up, that a call to an endpoint within the scope of the `openapi.yaml` file returns 200, that a call to a path outside that scope returns 200 if it exists and a 404 if not.
|
||||
- **routingTests.js** - Runs through all the endpoints defined in the `openapi.yaml`, and constructs a dummy request to send to the server. Confirms that the response code is 200. At this point requests containing xml or formData fail - currently they are not supported in the router.
|
||||
- **additionalEndpointsTests.js** - A test file for all the endpoints that are defined outside the openapi.yaml scope. Confirms that these endpoints return a successful 200 response.
|
||||
|
||||
|
||||
Future tests should be written to ensure that the response of every request sent should conform to the structure defined in the `openapi.yaml`. This test will fail 100% initially, and the job of the development team will be to clear these tests.
|
||||
|
||||
|
||||
#### models/
|
||||
Currently a concept awaiting feedback. The idea is to have the objects defined in the openapi.yaml act as models which are passed between the different modules. This will conform the programmers to interact using defined objects, rather than loosley-defined JSON objects. Given the nature of JavaScript progrmmers, who want to work with their own bootstrapped parameters, this concept might not work. Keeping this here for future discussion and feedback.
|
||||
|
||||
|
||||
|
802
samples/server/petstore/nodejs-express-server/api/openapi.yaml
Normal file
802
samples/server/petstore/nodejs-express-server/api/openapi.yaml
Normal file
@ -0,0 +1,802 @@
|
||||
openapi: 3.0.1
|
||||
info:
|
||||
description: This is a sample server Petstore server. For this sample, you can use
|
||||
the api key `special-key` to test the authorization filters.
|
||||
license:
|
||||
name: Apache-2.0
|
||||
url: http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
title: OpenAPI Petstore
|
||||
version: 1.0.0
|
||||
servers:
|
||||
- url: http://petstore.swagger.io/v2
|
||||
tags:
|
||||
- description: Everything about your Pets
|
||||
name: pet
|
||||
- description: Access to Petstore orders
|
||||
name: store
|
||||
- description: Operations about user
|
||||
name: user
|
||||
paths:
|
||||
/pet:
|
||||
post:
|
||||
operationId: addPet
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Pet'
|
||||
application/xml:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Pet'
|
||||
description: Pet object that needs to be added to the store
|
||||
required: true
|
||||
responses:
|
||||
405:
|
||||
content: {}
|
||||
description: Invalid input
|
||||
security:
|
||||
- petstore_auth:
|
||||
- write:pets
|
||||
- read:pets
|
||||
summary: Add a new pet to the store
|
||||
tags:
|
||||
- pet
|
||||
x-codegen-request-body-name: body
|
||||
x-openapi-router-controller: PetController
|
||||
x-openapi-router-service: PetService
|
||||
put:
|
||||
operationId: updatePet
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Pet'
|
||||
application/xml:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Pet'
|
||||
description: Pet object that needs to be added to the store
|
||||
required: true
|
||||
responses:
|
||||
400:
|
||||
content: {}
|
||||
description: Invalid ID supplied
|
||||
404:
|
||||
content: {}
|
||||
description: Pet not found
|
||||
405:
|
||||
content: {}
|
||||
description: Validation exception
|
||||
security:
|
||||
- petstore_auth:
|
||||
- write:pets
|
||||
- read:pets
|
||||
summary: Update an existing pet
|
||||
tags:
|
||||
- pet
|
||||
x-codegen-request-body-name: body
|
||||
x-openapi-router-controller: PetController
|
||||
x-openapi-router-service: PetService
|
||||
/pet/findByStatus:
|
||||
get:
|
||||
description: Multiple status values can be provided with comma separated strings
|
||||
operationId: findPetsByStatus
|
||||
parameters:
|
||||
- description: Status values that need to be considered for filter
|
||||
explode: false
|
||||
in: query
|
||||
name: status
|
||||
required: true
|
||||
schema:
|
||||
items:
|
||||
default: available
|
||||
enum:
|
||||
- available
|
||||
- pending
|
||||
- sold
|
||||
type: string
|
||||
type: array
|
||||
style: form
|
||||
responses:
|
||||
200:
|
||||
content:
|
||||
application/xml:
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/components/schemas/Pet'
|
||||
type: array
|
||||
application/json:
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/components/schemas/Pet'
|
||||
type: array
|
||||
description: successful operation
|
||||
400:
|
||||
content: {}
|
||||
description: Invalid status value
|
||||
security:
|
||||
- petstore_auth:
|
||||
- write:pets
|
||||
- read:pets
|
||||
summary: Finds Pets by status
|
||||
tags:
|
||||
- pet
|
||||
x-openapi-router-controller: PetController
|
||||
x-openapi-router-service: PetService
|
||||
/pet/findByTags:
|
||||
get:
|
||||
deprecated: true
|
||||
description: Multiple tags can be provided with comma separated strings. Use
|
||||
tag1, tag2, tag3 for testing.
|
||||
operationId: findPetsByTags
|
||||
parameters:
|
||||
- description: Tags to filter by
|
||||
explode: false
|
||||
in: query
|
||||
name: tags
|
||||
required: true
|
||||
schema:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
style: form
|
||||
responses:
|
||||
200:
|
||||
content:
|
||||
application/xml:
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/components/schemas/Pet'
|
||||
type: array
|
||||
application/json:
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/components/schemas/Pet'
|
||||
type: array
|
||||
description: successful operation
|
||||
400:
|
||||
content: {}
|
||||
description: Invalid tag value
|
||||
security:
|
||||
- petstore_auth:
|
||||
- write:pets
|
||||
- read:pets
|
||||
summary: Finds Pets by tags
|
||||
tags:
|
||||
- pet
|
||||
x-openapi-router-controller: PetController
|
||||
x-openapi-router-service: PetService
|
||||
/pet/{petId}:
|
||||
delete:
|
||||
operationId: deletePet
|
||||
parameters:
|
||||
- in: header
|
||||
name: api_key
|
||||
schema:
|
||||
type: string
|
||||
- description: Pet id to delete
|
||||
in: path
|
||||
name: petId
|
||||
required: true
|
||||
schema:
|
||||
format: int64
|
||||
type: integer
|
||||
responses:
|
||||
400:
|
||||
content: {}
|
||||
description: Invalid pet value
|
||||
security:
|
||||
- petstore_auth:
|
||||
- write:pets
|
||||
- read:pets
|
||||
summary: Deletes a pet
|
||||
tags:
|
||||
- pet
|
||||
x-openapi-router-controller: PetController
|
||||
x-openapi-router-service: PetService
|
||||
get:
|
||||
description: Returns a single pet
|
||||
operationId: getPetById
|
||||
parameters:
|
||||
- description: ID of pet to return
|
||||
in: path
|
||||
name: petId
|
||||
required: true
|
||||
schema:
|
||||
format: int64
|
||||
type: integer
|
||||
responses:
|
||||
200:
|
||||
content:
|
||||
application/xml:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Pet'
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Pet'
|
||||
description: successful operation
|
||||
400:
|
||||
content: {}
|
||||
description: Invalid ID supplied
|
||||
404:
|
||||
content: {}
|
||||
description: Pet not found
|
||||
security:
|
||||
- api_key: []
|
||||
summary: Find pet by ID
|
||||
tags:
|
||||
- pet
|
||||
x-openapi-router-controller: PetController
|
||||
x-openapi-router-service: PetService
|
||||
post:
|
||||
operationId: updatePetWithForm
|
||||
parameters:
|
||||
- description: ID of pet that needs to be updated
|
||||
in: path
|
||||
name: petId
|
||||
required: true
|
||||
schema:
|
||||
format: int64
|
||||
type: integer
|
||||
requestBody:
|
||||
content:
|
||||
application/x-www-form-urlencoded:
|
||||
schema:
|
||||
properties:
|
||||
name:
|
||||
description: Updated name of the pet
|
||||
type: string
|
||||
status:
|
||||
description: Updated status of the pet
|
||||
type: string
|
||||
responses:
|
||||
405:
|
||||
content: {}
|
||||
description: Invalid input
|
||||
security:
|
||||
- petstore_auth:
|
||||
- write:pets
|
||||
- read:pets
|
||||
summary: Updates a pet in the store with form data
|
||||
tags:
|
||||
- pet
|
||||
x-openapi-router-controller: PetController
|
||||
x-openapi-router-service: PetService
|
||||
/pet/{petId}/uploadImage:
|
||||
post:
|
||||
operationId: uploadFile
|
||||
parameters:
|
||||
- description: ID of pet to update
|
||||
in: path
|
||||
name: petId
|
||||
required: true
|
||||
schema:
|
||||
format: int64
|
||||
type: integer
|
||||
requestBody:
|
||||
content:
|
||||
multipart/form-data:
|
||||
schema:
|
||||
properties:
|
||||
additionalMetadata:
|
||||
description: Additional data to pass to server
|
||||
type: string
|
||||
file:
|
||||
description: file to upload
|
||||
format: binary
|
||||
type: string
|
||||
responses:
|
||||
200:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ApiResponse'
|
||||
description: successful operation
|
||||
security:
|
||||
- petstore_auth:
|
||||
- write:pets
|
||||
- read:pets
|
||||
summary: uploads an image
|
||||
tags:
|
||||
- pet
|
||||
x-openapi-router-controller: PetController
|
||||
x-openapi-router-service: PetService
|
||||
/store/inventory:
|
||||
get:
|
||||
description: Returns a map of status codes to quantities
|
||||
operationId: getInventory
|
||||
responses:
|
||||
200:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
additionalProperties:
|
||||
format: int32
|
||||
type: integer
|
||||
type: object
|
||||
description: successful operation
|
||||
security:
|
||||
- api_key: []
|
||||
summary: Returns pet inventories by status
|
||||
tags:
|
||||
- store
|
||||
x-openapi-router-controller: StoreController
|
||||
x-openapi-router-service: StoreService
|
||||
/store/order:
|
||||
post:
|
||||
operationId: placeOrder
|
||||
requestBody:
|
||||
content:
|
||||
'*/*':
|
||||
schema:
|
||||
$ref: '#/components/schemas/Order'
|
||||
description: order placed for purchasing the pet
|
||||
required: true
|
||||
responses:
|
||||
200:
|
||||
content:
|
||||
application/xml:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Order'
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Order'
|
||||
description: successful operation
|
||||
400:
|
||||
content: {}
|
||||
description: Invalid Order
|
||||
summary: Place an order for a pet
|
||||
tags:
|
||||
- store
|
||||
x-codegen-request-body-name: body
|
||||
x-openapi-router-controller: StoreController
|
||||
x-openapi-router-service: StoreService
|
||||
/store/order/{orderId}:
|
||||
delete:
|
||||
description: For valid response try integer IDs with value < 1000. Anything
|
||||
above 1000 or nonintegers will generate API errors
|
||||
operationId: deleteOrder
|
||||
parameters:
|
||||
- description: ID of the order that needs to be deleted
|
||||
in: path
|
||||
name: orderId
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
400:
|
||||
content: {}
|
||||
description: Invalid ID supplied
|
||||
404:
|
||||
content: {}
|
||||
description: Order not found
|
||||
summary: Delete purchase order by ID
|
||||
tags:
|
||||
- store
|
||||
x-openapi-router-controller: StoreController
|
||||
x-openapi-router-service: StoreService
|
||||
get:
|
||||
description: For valid response try integer IDs with value <= 5 or > 10. Other
|
||||
values will generated exceptions
|
||||
operationId: getOrderById
|
||||
parameters:
|
||||
- description: ID of pet that needs to be fetched
|
||||
in: path
|
||||
name: orderId
|
||||
required: true
|
||||
schema:
|
||||
format: int64
|
||||
maximum: 5
|
||||
minimum: 1
|
||||
type: integer
|
||||
responses:
|
||||
200:
|
||||
content:
|
||||
application/xml:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Order'
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Order'
|
||||
description: successful operation
|
||||
400:
|
||||
content: {}
|
||||
description: Invalid ID supplied
|
||||
404:
|
||||
content: {}
|
||||
description: Order not found
|
||||
summary: Find purchase order by ID
|
||||
tags:
|
||||
- store
|
||||
x-openapi-router-controller: StoreController
|
||||
x-openapi-router-service: StoreService
|
||||
/user:
|
||||
post:
|
||||
description: This can only be done by the logged in user.
|
||||
operationId: createUser
|
||||
requestBody:
|
||||
content:
|
||||
'*/*':
|
||||
schema:
|
||||
$ref: '#/components/schemas/User'
|
||||
description: Created user object
|
||||
required: true
|
||||
responses:
|
||||
default:
|
||||
content: {}
|
||||
description: successful operation
|
||||
summary: Create user
|
||||
tags:
|
||||
- user
|
||||
x-codegen-request-body-name: body
|
||||
x-openapi-router-controller: UserController
|
||||
x-openapi-router-service: UserService
|
||||
/user/createWithArray:
|
||||
post:
|
||||
operationId: createUsersWithArrayInput
|
||||
requestBody:
|
||||
content:
|
||||
'*/*':
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/components/schemas/User'
|
||||
type: array
|
||||
description: List of user object
|
||||
required: true
|
||||
responses:
|
||||
default:
|
||||
content: {}
|
||||
description: successful operation
|
||||
summary: Creates list of users with given input array
|
||||
tags:
|
||||
- user
|
||||
x-codegen-request-body-name: body
|
||||
x-openapi-router-controller: UserController
|
||||
x-openapi-router-service: UserService
|
||||
/user/createWithList:
|
||||
post:
|
||||
operationId: createUsersWithListInput
|
||||
requestBody:
|
||||
content:
|
||||
'*/*':
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/components/schemas/User'
|
||||
type: array
|
||||
description: List of user object
|
||||
required: true
|
||||
responses:
|
||||
default:
|
||||
content: {}
|
||||
description: successful operation
|
||||
summary: Creates list of users with given input array
|
||||
tags:
|
||||
- user
|
||||
x-codegen-request-body-name: body
|
||||
x-openapi-router-controller: UserController
|
||||
x-openapi-router-service: UserService
|
||||
/user/login:
|
||||
get:
|
||||
operationId: loginUser
|
||||
parameters:
|
||||
- description: The user name for login
|
||||
in: query
|
||||
name: username
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
- description: The password for login in clear text
|
||||
in: query
|
||||
name: password
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
200:
|
||||
content:
|
||||
application/xml:
|
||||
schema:
|
||||
type: string
|
||||
application/json:
|
||||
schema:
|
||||
type: string
|
||||
description: successful operation
|
||||
headers:
|
||||
X-Rate-Limit:
|
||||
description: calls per hour allowed by the user
|
||||
schema:
|
||||
format: int32
|
||||
type: integer
|
||||
X-Expires-After:
|
||||
description: date in UTC when toekn expires
|
||||
schema:
|
||||
format: date-time
|
||||
type: string
|
||||
400:
|
||||
content: {}
|
||||
description: Invalid username/password supplied
|
||||
summary: Logs user into the system
|
||||
tags:
|
||||
- user
|
||||
x-openapi-router-controller: UserController
|
||||
x-openapi-router-service: UserService
|
||||
/user/logout:
|
||||
get:
|
||||
operationId: logoutUser
|
||||
responses:
|
||||
default:
|
||||
content: {}
|
||||
description: successful operation
|
||||
summary: Logs out current logged in user session
|
||||
tags:
|
||||
- user
|
||||
x-openapi-router-controller: UserController
|
||||
x-openapi-router-service: UserService
|
||||
/user/{username}:
|
||||
delete:
|
||||
description: This can only be done by the logged in user.
|
||||
operationId: deleteUser
|
||||
parameters:
|
||||
- description: The name that needs to be deleted
|
||||
in: path
|
||||
name: username
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
400:
|
||||
content: {}
|
||||
description: Invalid username supplied
|
||||
404:
|
||||
content: {}
|
||||
description: User not found
|
||||
summary: Delete user
|
||||
tags:
|
||||
- user
|
||||
x-openapi-router-controller: UserController
|
||||
x-openapi-router-service: UserService
|
||||
get:
|
||||
operationId: getUserByName
|
||||
parameters:
|
||||
- description: The name that needs to be fetched. Use user1 for testing.
|
||||
in: path
|
||||
name: username
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
200:
|
||||
content:
|
||||
application/xml:
|
||||
schema:
|
||||
$ref: '#/components/schemas/User'
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/User'
|
||||
description: successful operation
|
||||
400:
|
||||
content: {}
|
||||
description: Invalid username supplied
|
||||
404:
|
||||
content: {}
|
||||
description: User not found
|
||||
summary: Get user by user name
|
||||
tags:
|
||||
- user
|
||||
x-openapi-router-controller: UserController
|
||||
x-openapi-router-service: UserService
|
||||
put:
|
||||
description: This can only be done by the logged in user.
|
||||
operationId: updateUser
|
||||
parameters:
|
||||
- description: name that need to be deleted
|
||||
in: path
|
||||
name: username
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
requestBody:
|
||||
content:
|
||||
'*/*':
|
||||
schema:
|
||||
$ref: '#/components/schemas/User'
|
||||
description: Updated user object
|
||||
required: true
|
||||
responses:
|
||||
400:
|
||||
content: {}
|
||||
description: Invalid user supplied
|
||||
404:
|
||||
content: {}
|
||||
description: User not found
|
||||
summary: Updated user
|
||||
tags:
|
||||
- user
|
||||
x-codegen-request-body-name: body
|
||||
x-openapi-router-controller: UserController
|
||||
x-openapi-router-service: UserService
|
||||
components:
|
||||
schemas:
|
||||
Order:
|
||||
description: An order for a pets from the pet store
|
||||
example:
|
||||
petId: 6
|
||||
quantity: 1
|
||||
id: 0
|
||||
shipDate: 2000-01-23T04:56:07.000+00:00
|
||||
complete: false
|
||||
status: placed
|
||||
properties:
|
||||
id:
|
||||
format: int64
|
||||
type: integer
|
||||
petId:
|
||||
format: int64
|
||||
type: integer
|
||||
quantity:
|
||||
format: int32
|
||||
type: integer
|
||||
shipDate:
|
||||
format: date-time
|
||||
type: string
|
||||
status:
|
||||
description: Order Status
|
||||
enum:
|
||||
- placed
|
||||
- approved
|
||||
- delivered
|
||||
type: string
|
||||
complete:
|
||||
default: false
|
||||
type: boolean
|
||||
title: Pet Order
|
||||
type: object
|
||||
xml:
|
||||
name: Order
|
||||
Category:
|
||||
description: A category for a pet
|
||||
example:
|
||||
name: name
|
||||
id: 6
|
||||
properties:
|
||||
id:
|
||||
format: int64
|
||||
type: integer
|
||||
name:
|
||||
type: string
|
||||
title: Pet category
|
||||
type: object
|
||||
xml:
|
||||
name: Category
|
||||
User:
|
||||
description: A User who is purchasing from the pet store
|
||||
example:
|
||||
firstName: firstName
|
||||
lastName: lastName
|
||||
password: password
|
||||
userStatus: 6
|
||||
phone: phone
|
||||
id: 0
|
||||
email: email
|
||||
username: username
|
||||
properties:
|
||||
id:
|
||||
format: int64
|
||||
type: integer
|
||||
username:
|
||||
type: string
|
||||
firstName:
|
||||
type: string
|
||||
lastName:
|
||||
type: string
|
||||
email:
|
||||
type: string
|
||||
password:
|
||||
type: string
|
||||
phone:
|
||||
type: string
|
||||
userStatus:
|
||||
description: User Status
|
||||
format: int32
|
||||
type: integer
|
||||
title: a User
|
||||
type: object
|
||||
xml:
|
||||
name: User
|
||||
Tag:
|
||||
description: A tag for a pet
|
||||
example:
|
||||
name: name
|
||||
id: 1
|
||||
properties:
|
||||
id:
|
||||
format: int64
|
||||
type: integer
|
||||
name:
|
||||
type: string
|
||||
title: Pet Tag
|
||||
type: object
|
||||
xml:
|
||||
name: Tag
|
||||
Pet:
|
||||
description: A pet for sale in the pet store
|
||||
example:
|
||||
photoUrls:
|
||||
- photoUrls
|
||||
- photoUrls
|
||||
name: doggie
|
||||
id: 0
|
||||
category:
|
||||
name: name
|
||||
id: 6
|
||||
tags:
|
||||
- name: name
|
||||
id: 1
|
||||
- name: name
|
||||
id: 1
|
||||
status: available
|
||||
properties:
|
||||
id:
|
||||
format: int64
|
||||
type: integer
|
||||
category:
|
||||
$ref: '#/components/schemas/Category'
|
||||
name:
|
||||
example: doggie
|
||||
type: string
|
||||
photoUrls:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
xml:
|
||||
name: photoUrl
|
||||
wrapped: true
|
||||
tags:
|
||||
items:
|
||||
$ref: '#/components/schemas/Tag'
|
||||
type: array
|
||||
xml:
|
||||
name: tag
|
||||
wrapped: true
|
||||
status:
|
||||
description: pet status in the store
|
||||
enum:
|
||||
- available
|
||||
- pending
|
||||
- sold
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
- photoUrls
|
||||
title: a Pet
|
||||
type: object
|
||||
xml:
|
||||
name: Pet
|
||||
ApiResponse:
|
||||
description: Describes the result of uploading an image resource
|
||||
example:
|
||||
code: 0
|
||||
type: type
|
||||
message: message
|
||||
properties:
|
||||
code:
|
||||
format: int32
|
||||
type: integer
|
||||
type:
|
||||
type: string
|
||||
message:
|
||||
type: string
|
||||
title: An uploaded response
|
||||
type: object
|
||||
securitySchemes:
|
||||
petstore_auth:
|
||||
flows:
|
||||
implicit:
|
||||
authorizationUrl: http://petstore.swagger.io/api/oauth/dialog
|
||||
scopes:
|
||||
write:pets: modify pets in your account
|
||||
read:pets: read your pets
|
||||
type: oauth2
|
||||
api_key:
|
||||
in: header
|
||||
name: api_key
|
||||
type: apiKey
|
795
samples/server/petstore/nodejs-express-server/api/swagger.yaml
Normal file
795
samples/server/petstore/nodejs-express-server/api/swagger.yaml
Normal file
@ -0,0 +1,795 @@
|
||||
---
|
||||
swagger: "2.0"
|
||||
info:
|
||||
description: |
|
||||
"This is a sample server Petstore server. You can find out more about\
|
||||
\ Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/).\
|
||||
\ For this sample, you can use the api key `special-key` to test the authorization\
|
||||
\ filters."
|
||||
version: "1.0.0"
|
||||
title: "Swagger Petstore"
|
||||
termsOfService: "http://swagger.io/terms/"
|
||||
contact:
|
||||
email: "apiteam@swagger.io"
|
||||
license:
|
||||
name: "Apache-2.0"
|
||||
url: "http://www.apache.org/licenses/LICENSE-2.0.html"
|
||||
basePath: "/v2"
|
||||
tags:
|
||||
- name: "pet"
|
||||
description: "Everything about your Pets"
|
||||
externalDocs:
|
||||
description: "Find out more"
|
||||
url: "http://swagger.io"
|
||||
- name: "store"
|
||||
description: "Access to Petstore orders"
|
||||
- name: "user"
|
||||
description: "Operations about user"
|
||||
externalDocs:
|
||||
description: "Find out more about our store"
|
||||
url: "http://swagger.io"
|
||||
schemes:
|
||||
- "http"
|
||||
paths:
|
||||
/pet:
|
||||
post:
|
||||
tags:
|
||||
- "pet"
|
||||
summary: "Add a new pet to the store"
|
||||
description: ""
|
||||
operationId: "addPet"
|
||||
consumes:
|
||||
- "application/json"
|
||||
- "application/xml"
|
||||
produces:
|
||||
- "application/xml"
|
||||
- "application/json"
|
||||
parameters:
|
||||
- in: "body"
|
||||
name: "body"
|
||||
description: "Pet object that needs to be added to the store"
|
||||
required: true
|
||||
schema:
|
||||
$ref: "#/definitions/Pet"
|
||||
responses:
|
||||
405:
|
||||
description: "Invalid input"
|
||||
security:
|
||||
- petstore_auth:
|
||||
- "write:pets"
|
||||
- "read:pets"
|
||||
x-swagger-router-controller: "Pet"
|
||||
put:
|
||||
tags:
|
||||
- "pet"
|
||||
summary: "Update an existing pet"
|
||||
description: ""
|
||||
operationId: "updatePet"
|
||||
consumes:
|
||||
- "application/json"
|
||||
- "application/xml"
|
||||
produces:
|
||||
- "application/xml"
|
||||
- "application/json"
|
||||
parameters:
|
||||
- in: "body"
|
||||
name: "body"
|
||||
description: "Pet object that needs to be added to the store"
|
||||
required: true
|
||||
schema:
|
||||
$ref: "#/definitions/Pet"
|
||||
responses:
|
||||
400:
|
||||
description: "Invalid ID supplied"
|
||||
404:
|
||||
description: "Pet not found"
|
||||
405:
|
||||
description: "Validation exception"
|
||||
security:
|
||||
- petstore_auth:
|
||||
- "write:pets"
|
||||
- "read:pets"
|
||||
x-swagger-router-controller: "Pet"
|
||||
/pet/findByStatus:
|
||||
get:
|
||||
tags:
|
||||
- "pet"
|
||||
summary: "Finds Pets by status"
|
||||
description: "Multiple status values can be provided with comma separated strings"
|
||||
operationId: "findPetsByStatus"
|
||||
produces:
|
||||
- "application/xml"
|
||||
- "application/json"
|
||||
parameters:
|
||||
- name: "status"
|
||||
in: "query"
|
||||
description: "Status values that need to be considered for filter"
|
||||
required: true
|
||||
type: "array"
|
||||
items:
|
||||
type: "string"
|
||||
default: "available"
|
||||
enum:
|
||||
- "available"
|
||||
- "pending"
|
||||
- "sold"
|
||||
collectionFormat: "csv"
|
||||
responses:
|
||||
200:
|
||||
description: "successful operation"
|
||||
schema:
|
||||
type: "array"
|
||||
items:
|
||||
$ref: "#/definitions/Pet"
|
||||
400:
|
||||
description: "Invalid status value"
|
||||
security:
|
||||
- petstore_auth:
|
||||
- "write:pets"
|
||||
- "read:pets"
|
||||
x-swagger-router-controller: "Pet"
|
||||
/pet/findByTags:
|
||||
get:
|
||||
tags:
|
||||
- "pet"
|
||||
summary: "Finds Pets by tags"
|
||||
description: |
|
||||
"Multiple tags can be provided with comma separated strings. Use\
|
||||
\ tag1, tag2, tag3 for testing."
|
||||
operationId: "findPetsByTags"
|
||||
produces:
|
||||
- "application/xml"
|
||||
- "application/json"
|
||||
parameters:
|
||||
- name: "tags"
|
||||
in: "query"
|
||||
description: "Tags to filter by"
|
||||
required: true
|
||||
type: "array"
|
||||
items:
|
||||
type: "string"
|
||||
collectionFormat: "csv"
|
||||
responses:
|
||||
200:
|
||||
description: "successful operation"
|
||||
schema:
|
||||
type: "array"
|
||||
items:
|
||||
$ref: "#/definitions/Pet"
|
||||
400:
|
||||
description: "Invalid tag value"
|
||||
security:
|
||||
- petstore_auth:
|
||||
- "write:pets"
|
||||
- "read:pets"
|
||||
deprecated: true
|
||||
x-swagger-router-controller: "Pet"
|
||||
/pet/{petId}:
|
||||
get:
|
||||
tags:
|
||||
- "pet"
|
||||
summary: "Find pet by ID"
|
||||
description: "Returns a single pet"
|
||||
operationId: "getPetById"
|
||||
produces:
|
||||
- "application/xml"
|
||||
- "application/json"
|
||||
parameters:
|
||||
- name: "petId"
|
||||
in: "path"
|
||||
description: "ID of pet to return"
|
||||
required: true
|
||||
type: "integer"
|
||||
format: "int64"
|
||||
responses:
|
||||
200:
|
||||
description: "successful operation"
|
||||
schema:
|
||||
$ref: "#/definitions/Pet"
|
||||
400:
|
||||
description: "Invalid ID supplied"
|
||||
404:
|
||||
description: "Pet not found"
|
||||
security:
|
||||
- api_key: []
|
||||
x-swagger-router-controller: "Pet"
|
||||
post:
|
||||
tags:
|
||||
- "pet"
|
||||
summary: "Updates a pet in the store with form data"
|
||||
description: ""
|
||||
operationId: "updatePetWithForm"
|
||||
consumes:
|
||||
- "application/x-www-form-urlencoded"
|
||||
produces:
|
||||
- "application/xml"
|
||||
- "application/json"
|
||||
parameters:
|
||||
- name: "petId"
|
||||
in: "path"
|
||||
description: "ID of pet that needs to be updated"
|
||||
required: true
|
||||
type: "integer"
|
||||
format: "int64"
|
||||
- name: "name"
|
||||
in: "formData"
|
||||
description: "Updated name of the pet"
|
||||
required: false
|
||||
type: "string"
|
||||
- name: "status"
|
||||
in: "formData"
|
||||
description: "Updated status of the pet"
|
||||
required: false
|
||||
type: "string"
|
||||
responses:
|
||||
405:
|
||||
description: "Invalid input"
|
||||
security:
|
||||
- petstore_auth:
|
||||
- "write:pets"
|
||||
- "read:pets"
|
||||
x-swagger-router-controller: "Pet"
|
||||
delete:
|
||||
tags:
|
||||
- "pet"
|
||||
summary: "Deletes a pet"
|
||||
description: ""
|
||||
operationId: "deletePet"
|
||||
produces:
|
||||
- "application/xml"
|
||||
- "application/json"
|
||||
parameters:
|
||||
- name: "api_key"
|
||||
in: "header"
|
||||
required: false
|
||||
type: "string"
|
||||
- name: "petId"
|
||||
in: "path"
|
||||
description: "Pet id to delete"
|
||||
required: true
|
||||
type: "integer"
|
||||
format: "int64"
|
||||
responses:
|
||||
400:
|
||||
description: "Invalid pet value"
|
||||
security:
|
||||
- petstore_auth:
|
||||
- "write:pets"
|
||||
- "read:pets"
|
||||
x-swagger-router-controller: "Pet"
|
||||
/pet/{petId}/uploadImage:
|
||||
post:
|
||||
tags:
|
||||
- "pet"
|
||||
summary: "uploads an image"
|
||||
description: ""
|
||||
operationId: "uploadFile"
|
||||
consumes:
|
||||
- "multipart/form-data"
|
||||
produces:
|
||||
- "application/json"
|
||||
parameters:
|
||||
- name: "petId"
|
||||
in: "path"
|
||||
description: "ID of pet to update"
|
||||
required: true
|
||||
type: "integer"
|
||||
format: "int64"
|
||||
- name: "additionalMetadata"
|
||||
in: "formData"
|
||||
description: "Additional data to pass to server"
|
||||
required: false
|
||||
type: "string"
|
||||
- name: "file"
|
||||
in: "formData"
|
||||
description: "file to upload"
|
||||
required: false
|
||||
type: "file"
|
||||
responses:
|
||||
200:
|
||||
description: "successful operation"
|
||||
schema:
|
||||
$ref: "#/definitions/ApiResponse"
|
||||
security:
|
||||
- petstore_auth:
|
||||
- "write:pets"
|
||||
- "read:pets"
|
||||
x-swagger-router-controller: "Pet"
|
||||
/store/inventory:
|
||||
get:
|
||||
tags:
|
||||
- "store"
|
||||
summary: "Returns pet inventories by status"
|
||||
description: "Returns a map of status codes to quantities"
|
||||
operationId: "getInventory"
|
||||
produces:
|
||||
- "application/json"
|
||||
parameters: []
|
||||
responses:
|
||||
200:
|
||||
description: "successful operation"
|
||||
schema:
|
||||
type: "object"
|
||||
additionalProperties:
|
||||
type: "integer"
|
||||
format: "int32"
|
||||
security:
|
||||
- api_key: []
|
||||
x-swagger-router-controller: "Store"
|
||||
/store/order:
|
||||
post:
|
||||
tags:
|
||||
- "store"
|
||||
summary: "Place an order for a pet"
|
||||
description: ""
|
||||
operationId: "placeOrder"
|
||||
produces:
|
||||
- "application/xml"
|
||||
- "application/json"
|
||||
parameters:
|
||||
- in: "body"
|
||||
name: "body"
|
||||
description: "order placed for purchasing the pet"
|
||||
required: true
|
||||
schema:
|
||||
$ref: "#/definitions/Order"
|
||||
responses:
|
||||
200:
|
||||
description: "successful operation"
|
||||
schema:
|
||||
$ref: "#/definitions/Order"
|
||||
400:
|
||||
description: "Invalid Order"
|
||||
x-swagger-router-controller: "Store"
|
||||
/store/order/{orderId}:
|
||||
get:
|
||||
tags:
|
||||
- "store"
|
||||
summary: "Find purchase order by ID"
|
||||
description: |
|
||||
"For valid response try integer IDs with value <= 5 or > 10. Other\
|
||||
\ values will generated exceptions"
|
||||
operationId: "getOrderById"
|
||||
produces:
|
||||
- "application/xml"
|
||||
- "application/json"
|
||||
parameters:
|
||||
- name: "orderId"
|
||||
in: "path"
|
||||
description: "ID of pet that needs to be fetched"
|
||||
required: true
|
||||
type: "integer"
|
||||
maximum: 5
|
||||
minimum: 1
|
||||
format: "int64"
|
||||
responses:
|
||||
200:
|
||||
description: "successful operation"
|
||||
schema:
|
||||
$ref: "#/definitions/Order"
|
||||
400:
|
||||
description: "Invalid ID supplied"
|
||||
404:
|
||||
description: "Order not found"
|
||||
x-swagger-router-controller: "Store"
|
||||
delete:
|
||||
tags:
|
||||
- "store"
|
||||
summary: "Delete purchase order by ID"
|
||||
description: |
|
||||
"For valid response try integer IDs with value < 1000. Anything\
|
||||
\ above 1000 or nonintegers will generate API errors"
|
||||
operationId: "deleteOrder"
|
||||
produces:
|
||||
- "application/xml"
|
||||
- "application/json"
|
||||
parameters:
|
||||
- name: "orderId"
|
||||
in: "path"
|
||||
description: "ID of the order that needs to be deleted"
|
||||
required: true
|
||||
type: "string"
|
||||
responses:
|
||||
400:
|
||||
description: "Invalid ID supplied"
|
||||
404:
|
||||
description: "Order not found"
|
||||
x-swagger-router-controller: "Store"
|
||||
/user:
|
||||
post:
|
||||
tags:
|
||||
- "user"
|
||||
summary: "Create user"
|
||||
description: "This can only be done by the logged in user."
|
||||
operationId: "createUser"
|
||||
produces:
|
||||
- "application/xml"
|
||||
- "application/json"
|
||||
parameters:
|
||||
- in: "body"
|
||||
name: "body"
|
||||
description: "Created user object"
|
||||
required: true
|
||||
schema:
|
||||
$ref: "#/definitions/User"
|
||||
responses:
|
||||
default:
|
||||
description: "successful operation"
|
||||
x-swagger-router-controller: "User"
|
||||
/user/createWithArray:
|
||||
post:
|
||||
tags:
|
||||
- "user"
|
||||
summary: "Creates list of users with given input array"
|
||||
description: ""
|
||||
operationId: "createUsersWithArrayInput"
|
||||
produces:
|
||||
- "application/xml"
|
||||
- "application/json"
|
||||
parameters:
|
||||
- in: "body"
|
||||
name: "body"
|
||||
description: "List of user object"
|
||||
required: true
|
||||
schema:
|
||||
type: "array"
|
||||
items:
|
||||
$ref: "#/definitions/User"
|
||||
responses:
|
||||
default:
|
||||
description: "successful operation"
|
||||
x-swagger-router-controller: "User"
|
||||
/user/createWithList:
|
||||
post:
|
||||
tags:
|
||||
- "user"
|
||||
summary: "Creates list of users with given input array"
|
||||
description: ""
|
||||
operationId: "createUsersWithListInput"
|
||||
produces:
|
||||
- "application/xml"
|
||||
- "application/json"
|
||||
parameters:
|
||||
- in: "body"
|
||||
name: "body"
|
||||
description: "List of user object"
|
||||
required: true
|
||||
schema:
|
||||
type: "array"
|
||||
items:
|
||||
$ref: "#/definitions/User"
|
||||
responses:
|
||||
default:
|
||||
description: "successful operation"
|
||||
x-swagger-router-controller: "User"
|
||||
/user/login:
|
||||
get:
|
||||
tags:
|
||||
- "user"
|
||||
summary: "Logs user into the system"
|
||||
description: ""
|
||||
operationId: "loginUser"
|
||||
produces:
|
||||
- "application/xml"
|
||||
- "application/json"
|
||||
parameters:
|
||||
- name: "username"
|
||||
in: "query"
|
||||
description: "The user name for login"
|
||||
required: true
|
||||
type: "string"
|
||||
- name: "password"
|
||||
in: "query"
|
||||
description: "The password for login in clear text"
|
||||
required: true
|
||||
type: "string"
|
||||
responses:
|
||||
200:
|
||||
description: "successful operation"
|
||||
schema:
|
||||
type: "string"
|
||||
headers:
|
||||
X-Rate-Limit:
|
||||
type: "integer"
|
||||
format: "int32"
|
||||
description: "calls per hour allowed by the user"
|
||||
X-Expires-After:
|
||||
type: "string"
|
||||
format: "date-time"
|
||||
description: "date in UTC when toekn expires"
|
||||
400:
|
||||
description: "Invalid username/password supplied"
|
||||
x-swagger-router-controller: "User"
|
||||
/user/logout:
|
||||
get:
|
||||
tags:
|
||||
- "user"
|
||||
summary: "Logs out current logged in user session"
|
||||
description: ""
|
||||
operationId: "logoutUser"
|
||||
produces:
|
||||
- "application/xml"
|
||||
- "application/json"
|
||||
parameters: []
|
||||
responses:
|
||||
default:
|
||||
description: "successful operation"
|
||||
x-swagger-router-controller: "User"
|
||||
/user/{username}:
|
||||
get:
|
||||
tags:
|
||||
- "user"
|
||||
summary: "Get user by user name"
|
||||
description: ""
|
||||
operationId: "getUserByName"
|
||||
produces:
|
||||
- "application/xml"
|
||||
- "application/json"
|
||||
parameters:
|
||||
- name: "username"
|
||||
in: "path"
|
||||
description: "The name that needs to be fetched. Use user1 for testing."
|
||||
required: true
|
||||
type: "string"
|
||||
responses:
|
||||
200:
|
||||
description: "successful operation"
|
||||
schema:
|
||||
$ref: "#/definitions/User"
|
||||
400:
|
||||
description: "Invalid username supplied"
|
||||
404:
|
||||
description: "User not found"
|
||||
x-swagger-router-controller: "User"
|
||||
put:
|
||||
tags:
|
||||
- "user"
|
||||
summary: "Updated user"
|
||||
description: "This can only be done by the logged in user."
|
||||
operationId: "updateUser"
|
||||
produces:
|
||||
- "application/xml"
|
||||
- "application/json"
|
||||
parameters:
|
||||
- name: "username"
|
||||
in: "path"
|
||||
description: "name that need to be deleted"
|
||||
required: true
|
||||
type: "string"
|
||||
- in: "body"
|
||||
name: "body"
|
||||
description: "Updated user object"
|
||||
required: true
|
||||
schema:
|
||||
$ref: "#/definitions/User"
|
||||
responses:
|
||||
400:
|
||||
description: "Invalid user supplied"
|
||||
404:
|
||||
description: "User not found"
|
||||
x-swagger-router-controller: "User"
|
||||
delete:
|
||||
tags:
|
||||
- "user"
|
||||
summary: "Delete user"
|
||||
description: "This can only be done by the logged in user."
|
||||
operationId: "deleteUser"
|
||||
produces:
|
||||
- "application/xml"
|
||||
- "application/json"
|
||||
parameters:
|
||||
- name: "username"
|
||||
in: "path"
|
||||
description: "The name that needs to be deleted"
|
||||
required: true
|
||||
type: "string"
|
||||
responses:
|
||||
400:
|
||||
description: "Invalid username supplied"
|
||||
404:
|
||||
description: "User not found"
|
||||
x-swagger-router-controller: "User"
|
||||
|
||||
securityDefinitions:
|
||||
petstore_auth:
|
||||
type: "oauth2"
|
||||
authorizationUrl: "http://petstore.swagger.io/api/oauth/dialog"
|
||||
flow: "implicit"
|
||||
scopes:
|
||||
write:pets: "modify pets in your account"
|
||||
read:pets: "read your pets"
|
||||
api_key:
|
||||
type: "apiKey"
|
||||
name: "api_key"
|
||||
in: "header"
|
||||
definitions:
|
||||
Order:
|
||||
type: "object"
|
||||
properties:
|
||||
id:
|
||||
type: "integer"
|
||||
format: "int64"
|
||||
petId:
|
||||
type: "integer"
|
||||
format: "int64"
|
||||
quantity:
|
||||
type: "integer"
|
||||
format: "int32"
|
||||
shipDate:
|
||||
type: "string"
|
||||
format: "date-time"
|
||||
status:
|
||||
type: "string"
|
||||
description: "Order Status"
|
||||
enum:
|
||||
- "placed"
|
||||
- "approved"
|
||||
- "delivered"
|
||||
complete:
|
||||
type: "boolean"
|
||||
default: false
|
||||
title: "Pet Order"
|
||||
description: "An order for a pets from the pet store"
|
||||
example:
|
||||
petId: 6
|
||||
quantity: 1
|
||||
id: 0
|
||||
shipDate: "2000-01-23T04:56:07.000+00:00"
|
||||
complete: false
|
||||
status: "placed"
|
||||
xml:
|
||||
name: "Order"
|
||||
Category:
|
||||
type: "object"
|
||||
properties:
|
||||
id:
|
||||
type: "integer"
|
||||
format: "int64"
|
||||
name:
|
||||
type: "string"
|
||||
title: "Pet category"
|
||||
description: "A category for a pet"
|
||||
example:
|
||||
name: "name"
|
||||
id: 6
|
||||
xml:
|
||||
name: "Category"
|
||||
User:
|
||||
type: "object"
|
||||
properties:
|
||||
id:
|
||||
type: "integer"
|
||||
format: "int64"
|
||||
username:
|
||||
type: "string"
|
||||
firstName:
|
||||
type: "string"
|
||||
lastName:
|
||||
type: "string"
|
||||
email:
|
||||
type: "string"
|
||||
password:
|
||||
type: "string"
|
||||
phone:
|
||||
type: "string"
|
||||
userStatus:
|
||||
type: "integer"
|
||||
format: "int32"
|
||||
description: "User Status"
|
||||
title: "a User"
|
||||
description: "A User who is purchasing from the pet store"
|
||||
example:
|
||||
firstName: "firstName"
|
||||
lastName: "lastName"
|
||||
password: "password"
|
||||
userStatus: 6
|
||||
phone: "phone"
|
||||
id: 0
|
||||
email: "email"
|
||||
username: "username"
|
||||
xml:
|
||||
name: "User"
|
||||
Tag:
|
||||
type: "object"
|
||||
properties:
|
||||
id:
|
||||
type: "integer"
|
||||
format: "int64"
|
||||
name:
|
||||
type: "string"
|
||||
title: "Pet Tag"
|
||||
description: "A tag for a pet"
|
||||
example:
|
||||
name: "name"
|
||||
id: 1
|
||||
xml:
|
||||
name: "Tag"
|
||||
Pet:
|
||||
type: "object"
|
||||
required:
|
||||
- "name"
|
||||
- "photoUrls"
|
||||
properties:
|
||||
id:
|
||||
type: "integer"
|
||||
format: "int64"
|
||||
category:
|
||||
$ref: "#/definitions/Category"
|
||||
name:
|
||||
type: "string"
|
||||
example: "doggie"
|
||||
photoUrls:
|
||||
type: "array"
|
||||
xml:
|
||||
name: "photoUrl"
|
||||
wrapped: true
|
||||
items:
|
||||
type: "string"
|
||||
tags:
|
||||
type: "array"
|
||||
xml:
|
||||
name: "tag"
|
||||
wrapped: true
|
||||
items:
|
||||
$ref: "#/definitions/Tag"
|
||||
status:
|
||||
type: "string"
|
||||
description: "pet status in the store"
|
||||
enum:
|
||||
- "available"
|
||||
- "pending"
|
||||
- "sold"
|
||||
title: "a Pet"
|
||||
description: "A pet for sale in the pet store"
|
||||
example:
|
||||
photoUrls:
|
||||
- "photoUrls"
|
||||
- "photoUrls"
|
||||
name: "doggie"
|
||||
id: 0
|
||||
category:
|
||||
name: "name"
|
||||
id: 6
|
||||
tags:
|
||||
- name: "name"
|
||||
id: 1
|
||||
- name: "name"
|
||||
id: 1
|
||||
status: "available"
|
||||
xml:
|
||||
name: "Pet"
|
||||
ApiResponse:
|
||||
type: "object"
|
||||
properties:
|
||||
code:
|
||||
type: "integer"
|
||||
format: "int32"
|
||||
type:
|
||||
type: "string"
|
||||
message:
|
||||
type: "string"
|
||||
title: "An uploaded response"
|
||||
description: "Describes the result of uploading an image resource"
|
||||
example:
|
||||
code: 0
|
||||
type: "type"
|
||||
message: "message"
|
||||
testItem:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
name:
|
||||
type: string
|
||||
descrtiption:
|
||||
type: string
|
||||
version:
|
||||
type: number
|
||||
example:
|
||||
id: 1
|
||||
name: "testItem"
|
||||
description: "An item which means very little, as it's only a test"
|
||||
version: 2.3
|
||||
externalDocs:
|
||||
description: "Find out more about Swagger"
|
||||
url: "http://swagger.io"
|
12
samples/server/petstore/nodejs-express-server/config.js
Normal file
12
samples/server/petstore/nodejs-express-server/config.js
Normal file
@ -0,0 +1,12 @@
|
||||
const path = require('path');
|
||||
|
||||
const config = {
|
||||
ROOT_DIR: __dirname,
|
||||
URL_PORT: 3000,
|
||||
URL_PATH: 'http://localhost',
|
||||
BASE_VERSION: 'v2',
|
||||
CONTROLLER_DIRECTORY: path.join(__dirname, 'controllers'),
|
||||
};
|
||||
config.OPENAPI_YAML = path.join(config.ROOT_DIR, 'api', 'openapi.yaml');
|
||||
config.FULL_PATH = `${config.URL_PATH}:${config.URL_PORT}/${config.BASE_VERSION}`;
|
||||
module.exports = config;
|
@ -0,0 +1,72 @@
|
||||
const logger = require('../logger');
|
||||
|
||||
class Controller {
|
||||
static sendResponse(response, payload) {
|
||||
/**
|
||||
* The default response-code is 200. We want to allow to change that. in That case,
|
||||
* payload will be an object consisting of a code and a payload. If not customized
|
||||
* send 200 and the payload as received in this method.
|
||||
*/
|
||||
response.status(payload.code || 200);
|
||||
const responsePayload = payload.payload !== undefined ? payload.payload : payload;
|
||||
if (responsePayload instanceof Object) {
|
||||
response.json(responsePayload);
|
||||
} else {
|
||||
response.end(responsePayload);
|
||||
}
|
||||
}
|
||||
|
||||
static sendError(response, error) {
|
||||
response.status(error.code || 500);
|
||||
if (error.error instanceof Object) {
|
||||
response.json(error.error);
|
||||
} else {
|
||||
response.end(error.error || error.message);
|
||||
}
|
||||
}
|
||||
|
||||
static collectFiles(request) {
|
||||
logger.info('Checking if files are expected in schema');
|
||||
if (request.openapi.schema.requestBody !== undefined) {
|
||||
const [contentType] = request.headers['content-type'].split(';');
|
||||
if (contentType === 'multipart/form-data') {
|
||||
const contentSchema = request.openapi.schema.requestBody.content[contentType].schema;
|
||||
Object.entries(contentSchema.properties).forEach(([name, property]) => {
|
||||
if (property.type === 'string' && ['binary', 'base64'].indexOf(property.format) > -1) {
|
||||
request.body[name] = request.files.find(file => file.fieldname === name);
|
||||
}
|
||||
});
|
||||
} else if (request.openapi.schema.requestBody.content[contentType] !== undefined
|
||||
&& request.files !== undefined) {
|
||||
[request.body] = request.files;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static collectRequestParams(request) {
|
||||
this.collectFiles(request);
|
||||
const requestParams = {};
|
||||
if (request.openapi.schema.requestBody !== undefined) {
|
||||
requestParams.body = request.body;
|
||||
}
|
||||
request.openapi.schema.parameters.forEach((param) => {
|
||||
if (param.in === 'path') {
|
||||
requestParams[param.name] = request.openapi.pathParams[param.name];
|
||||
} else if (param.in === 'query') {
|
||||
requestParams[param.name] = request.query[param.name];
|
||||
}
|
||||
});
|
||||
return requestParams;
|
||||
}
|
||||
|
||||
static async handleRequest(request, response, serviceOperation) {
|
||||
try {
|
||||
const serviceResponse = await serviceOperation(this.collectRequestParams(request));
|
||||
Controller.sendResponse(response, serviceResponse);
|
||||
} catch (error) {
|
||||
Controller.sendError(response, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Controller;
|
@ -0,0 +1,42 @@
|
||||
const Controller = require('./Controller');
|
||||
|
||||
class PetController {
|
||||
constructor(Service) {
|
||||
this.service = Service;
|
||||
}
|
||||
|
||||
async addPet(request, response) {
|
||||
await Controller.handleRequest(request, response, this.service.addPet);
|
||||
}
|
||||
|
||||
async deletePet(request, response) {
|
||||
await Controller.handleRequest(request, response, this.service.deletePet);
|
||||
}
|
||||
|
||||
async findPetsByStatus(request, response) {
|
||||
await Controller.handleRequest(request, response, this.service.findPetsByStatus);
|
||||
}
|
||||
|
||||
async findPetsByTags(request, response) {
|
||||
await Controller.handleRequest(request, response, this.service.findPetsByTags);
|
||||
}
|
||||
|
||||
async getPetById(request, response) {
|
||||
await Controller.handleRequest(request, response, this.service.getPetById);
|
||||
}
|
||||
|
||||
async updatePet(request, response) {
|
||||
await Controller.handleRequest(request, response, this.service.updatePet);
|
||||
}
|
||||
|
||||
async updatePetWithForm(request, response) {
|
||||
await Controller.handleRequest(request, response, this.service.updatePetWithForm);
|
||||
}
|
||||
|
||||
async uploadFile(request, response) {
|
||||
await Controller.handleRequest(request, response, this.service.uploadFile);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = PetController;
|
@ -0,0 +1,26 @@
|
||||
const Controller = require('./Controller');
|
||||
|
||||
class StoreController {
|
||||
constructor(Service) {
|
||||
this.service = Service;
|
||||
}
|
||||
|
||||
async deleteOrder(request, response) {
|
||||
await Controller.handleRequest(request, response, this.service.deleteOrder);
|
||||
}
|
||||
|
||||
async getInventory(request, response) {
|
||||
await Controller.handleRequest(request, response, this.service.getInventory);
|
||||
}
|
||||
|
||||
async getOrderById(request, response) {
|
||||
await Controller.handleRequest(request, response, this.service.getOrderById);
|
||||
}
|
||||
|
||||
async placeOrder(request, response) {
|
||||
await Controller.handleRequest(request, response, this.service.placeOrder);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = StoreController;
|
@ -0,0 +1,42 @@
|
||||
const Controller = require('./Controller');
|
||||
|
||||
class UserController {
|
||||
constructor(Service) {
|
||||
this.service = Service;
|
||||
}
|
||||
|
||||
async createUser(request, response) {
|
||||
await Controller.handleRequest(request, response, this.service.createUser);
|
||||
}
|
||||
|
||||
async createUsersWithArrayInput(request, response) {
|
||||
await Controller.handleRequest(request, response, this.service.createUsersWithArrayInput);
|
||||
}
|
||||
|
||||
async createUsersWithListInput(request, response) {
|
||||
await Controller.handleRequest(request, response, this.service.createUsersWithListInput);
|
||||
}
|
||||
|
||||
async deleteUser(request, response) {
|
||||
await Controller.handleRequest(request, response, this.service.deleteUser);
|
||||
}
|
||||
|
||||
async getUserByName(request, response) {
|
||||
await Controller.handleRequest(request, response, this.service.getUserByName);
|
||||
}
|
||||
|
||||
async loginUser(request, response) {
|
||||
await Controller.handleRequest(request, response, this.service.loginUser);
|
||||
}
|
||||
|
||||
async logoutUser(request, response) {
|
||||
await Controller.handleRequest(request, response, this.service.logoutUser);
|
||||
}
|
||||
|
||||
async updateUser(request, response) {
|
||||
await Controller.handleRequest(request, response, this.service.updateUser);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = UserController;
|
@ -0,0 +1,9 @@
|
||||
const PetController = require('./PetController');
|
||||
const StoreController = require('./StoreController');
|
||||
const UserController = require('./UserController');
|
||||
|
||||
module.exports = {
|
||||
PetController,
|
||||
StoreController,
|
||||
UserController,
|
||||
};
|
@ -0,0 +1,93 @@
|
||||
// const { Middleware } = require('swagger-express-middleware');
|
||||
const path = require('path');
|
||||
const swaggerUI = require('swagger-ui-express');
|
||||
const yamljs = require('yamljs');
|
||||
const express = require('express');
|
||||
const cors = require('cors');
|
||||
const cookieParser = require('cookie-parser');
|
||||
const bodyParser = require('body-parser');
|
||||
const { OpenApiValidator } = require('express-openapi-validator');
|
||||
const openapiRouter = require('./utils/openapiRouter');
|
||||
const logger = require('./logger');
|
||||
|
||||
class ExpressServer {
|
||||
constructor(port, openApiYaml) {
|
||||
this.port = port;
|
||||
this.app = express();
|
||||
this.openApiPath = openApiYaml;
|
||||
this.schema = yamljs.load(openApiYaml);
|
||||
this.setupMiddleware();
|
||||
}
|
||||
|
||||
setupMiddleware() {
|
||||
// this.setupAllowedMedia();
|
||||
this.app.use(cors());
|
||||
this.app.use(bodyParser.json());
|
||||
this.app.use(express.json());
|
||||
this.app.use(express.urlencoded({ extended: false }));
|
||||
this.app.use(cookieParser());
|
||||
this.app.use('/spec', express.static(path.join(__dirname, 'api')));
|
||||
this.app.get('/hello', (req, res) => res.send('Hello World. path: '+this.openApiPath));
|
||||
// this.app.get('/spec', express.static(this.openApiPath));
|
||||
this.app.use('/api-docs', swaggerUI.serve, swaggerUI.setup(this.schema));
|
||||
this.app.get('/login-redirect', (req, res) => {
|
||||
res.status(200);
|
||||
res.json(req.query);
|
||||
});
|
||||
this.app.get('/oauth2-redirect.html', (req, res) => {
|
||||
res.status(200);
|
||||
res.json(req.query);
|
||||
});
|
||||
new OpenApiValidator({
|
||||
apiSpecPath: this.openApiPath,
|
||||
}).install(this.app);
|
||||
this.app.use(openapiRouter());
|
||||
this.app.get('/', (req, res) => {
|
||||
res.status(200);
|
||||
res.end('Hello World');
|
||||
});
|
||||
}
|
||||
|
||||
addErrorHandler() {
|
||||
this.app.use('*', (req, res) => {
|
||||
res.status(404);
|
||||
res.send(JSON.stringify({ error: `path ${req.baseUrl} doesn't exist` }));
|
||||
});
|
||||
/**
|
||||
* suppressed eslint rule: The next variable is required here, even though it's not used.
|
||||
*
|
||||
** */
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
this.app.use((error, req, res, next) => {
|
||||
const errorResponse = error.error || error.errors || error.message || 'Unknown error';
|
||||
res.status(error.status || 500);
|
||||
res.type('json');
|
||||
res.json({ error: errorResponse });
|
||||
});
|
||||
}
|
||||
|
||||
async launch() {
|
||||
return new Promise(
|
||||
async (resolve, reject) => {
|
||||
try {
|
||||
this.addErrorHandler();
|
||||
this.server = await this.app.listen(this.port, () => {
|
||||
console.log(`server running on port ${this.port}`);
|
||||
resolve(this.server);
|
||||
});
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
async close() {
|
||||
if (this.server !== undefined) {
|
||||
await this.server.close();
|
||||
console.log(`Server on port ${this.port} shut down`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ExpressServer;
|
28
samples/server/petstore/nodejs-express-server/index.js
Normal file
28
samples/server/petstore/nodejs-express-server/index.js
Normal file
@ -0,0 +1,28 @@
|
||||
const config = require('./config');
|
||||
const logger = require('./logger');
|
||||
const ExpressServer = require('./expressServer');
|
||||
// const App = require('./app');
|
||||
|
||||
// const app = new App(config);
|
||||
// app.launch()
|
||||
// .then(() => {
|
||||
// logger.info('Server launched');
|
||||
// })
|
||||
// .catch((error) => {
|
||||
// logger.error('found error, shutting down server');
|
||||
// app.close()
|
||||
// .catch(closeError => logger.error(closeError))
|
||||
// .finally(() => logger.error(error));
|
||||
// });
|
||||
const launchServer = async () => {
|
||||
try {
|
||||
this.expressServer = new ExpressServer(config.URL_PORT, config.OPENAPI_YAML);
|
||||
await this.expressServer.launch();
|
||||
logger.info('Express server running');
|
||||
} catch (error) {
|
||||
logger.error(error);
|
||||
await this.close();
|
||||
}
|
||||
};
|
||||
|
||||
launchServer().catch(e => logger.error(e));
|
17
samples/server/petstore/nodejs-express-server/logger.js
Normal file
17
samples/server/petstore/nodejs-express-server/logger.js
Normal file
@ -0,0 +1,17 @@
|
||||
const winston = require('winston');
|
||||
|
||||
const logger = winston.createLogger({
|
||||
level: 'info',
|
||||
format: winston.format.json(),
|
||||
defaultMeta: { service: 'user-service' },
|
||||
transports: [
|
||||
new winston.transports.File({ filename: 'error.log', level: 'error' }),
|
||||
new winston.transports.File({ filename: 'combined.log' }),
|
||||
],
|
||||
});
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
logger.add(new winston.transports.Console({ format: winston.format.simple() }));
|
||||
}
|
||||
|
||||
module.exports = logger;
|
@ -0,0 +1,23 @@
|
||||
const ono = require('ono');
|
||||
const Model = require('./Model');
|
||||
|
||||
class Category {
|
||||
constructor(id, name) {
|
||||
const validationErrors = (Model.validateModel(Category, { name, id }));
|
||||
if (validationErrors.length === 0) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
} else {
|
||||
throw ono('Tried to create an invalid Category instance', { errors: validationErrors });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Category.types = {
|
||||
id: 'integer',
|
||||
name: 'string',
|
||||
};
|
||||
|
||||
Category.required = [];
|
||||
|
||||
module.exports = Category;
|
@ -0,0 +1,47 @@
|
||||
class Model {
|
||||
static validateModel(modelClass, variables) {
|
||||
const invalidArray = [];
|
||||
Object.entries(variables).forEach(([key, value]) => {
|
||||
const typeToCheck = modelClass.types[key];
|
||||
switch (typeToCheck) {
|
||||
case 'string':
|
||||
if (!(typeof value === 'string' || value instanceof String)) {
|
||||
invalidArray.push({ key, expectedType: typeToCheck, value });
|
||||
}
|
||||
break;
|
||||
case 'number':
|
||||
case 'integer':
|
||||
if (!(typeof value === 'number' && !Number.isNaN(value))) {
|
||||
invalidArray.push({ key, expectedType: typeToCheck, value });
|
||||
}
|
||||
break;
|
||||
case 'array':
|
||||
if (!(value && typeof value === 'object' && value.constructor === Array)) {
|
||||
invalidArray.push({ key, expectedType: typeToCheck, value });
|
||||
}
|
||||
break;
|
||||
case 'object':
|
||||
if (!(value && typeof value === 'object' && value.constructor === Object)) {
|
||||
invalidArray.push({ key, expectedType: typeToCheck, value });
|
||||
}
|
||||
break;
|
||||
case 'boolean':
|
||||
if (!(typeof value === 'boolean')) {
|
||||
invalidArray.push({ key, expectedType: typeToCheck, value });
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
modelClass.required.forEach((requiredFieldName) => {
|
||||
if (variables[requiredFieldName] === undefined || variables[requiredFieldName] === '') {
|
||||
invalidArray.push(
|
||||
{ field: requiredFieldName, required: true, value: variables[requiredFieldName] },
|
||||
);
|
||||
}
|
||||
});
|
||||
return invalidArray;
|
||||
}
|
||||
}
|
||||
module.exports = Model;
|
35
samples/server/petstore/nodejs-express-server/models/Pet.js
Normal file
35
samples/server/petstore/nodejs-express-server/models/Pet.js
Normal file
@ -0,0 +1,35 @@
|
||||
const ono = require('ono');
|
||||
const Model = require('./Model');
|
||||
const Tag = require('./Tag');
|
||||
const Category = require('./Category');
|
||||
|
||||
class Pet {
|
||||
constructor(photoUrls, name, id, tags, status, category) {
|
||||
const validationErrors = Model.validateModel(Pet, {
|
||||
photoUrls, name, id, tags, status, category,
|
||||
});
|
||||
if (validationErrors.length === 0) {
|
||||
this.photoUrls = photoUrls;
|
||||
this.name = name;
|
||||
this.id = id;
|
||||
this.tags = tags.map(t => new Tag(t.id, t.name));
|
||||
this.status = status;
|
||||
this.category = new Category(category.id, category.name);
|
||||
} else {
|
||||
throw ono('Tried to create an invalid Pet instance', { errors: validationErrors });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Pet.types = {
|
||||
photoUrls: 'array',
|
||||
name: 'string',
|
||||
id: 'integer',
|
||||
tags: 'array',
|
||||
status: 'string',
|
||||
category: 'object',
|
||||
};
|
||||
|
||||
Pet.required = ['name', 'photoUrls'];
|
||||
|
||||
module.exports = Pet;
|
24
samples/server/petstore/nodejs-express-server/models/Tag.js
Normal file
24
samples/server/petstore/nodejs-express-server/models/Tag.js
Normal file
@ -0,0 +1,24 @@
|
||||
const ono = require('ono');
|
||||
const Model = require('./Model');
|
||||
|
||||
class Tag {
|
||||
constructor(id, name) {
|
||||
const validationErrors = Model.validateModel(Tag,
|
||||
{ name, id });
|
||||
if (validationErrors.length === 0) {
|
||||
this.name = name;
|
||||
this.id = id;
|
||||
} else {
|
||||
throw ono('Tried to create an invalid Tag instance', { errors: validationErrors });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Tag.types = {
|
||||
name: 'string',
|
||||
id: 'integer',
|
||||
};
|
||||
|
||||
Tag.required = [];
|
||||
|
||||
module.exports = Tag;
|
@ -0,0 +1,25 @@
|
||||
const ono = require('ono');
|
||||
const Model = require('./Model');
|
||||
|
||||
class UpdatePetWithFormModel {
|
||||
constructor(name, status) {
|
||||
const validationErrors = Model.validateModel(UpdatePetWithFormModel, {
|
||||
name, status,
|
||||
});
|
||||
if (validationErrors.length === 0) {
|
||||
this.name = name;
|
||||
this.status = status;
|
||||
} else {
|
||||
throw ono('Tried to create an invalid UpdatePetWithFormModel instance', { errors: validationErrors });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UpdatePetWithFormModel.types = {
|
||||
name: 'string',
|
||||
status: 'string',
|
||||
};
|
||||
|
||||
UpdatePetWithFormModel.required = [];
|
||||
|
||||
module.exports = UpdatePetWithFormModel;
|
@ -0,0 +1,25 @@
|
||||
const ono = require('ono');
|
||||
const Model = require('./Model');
|
||||
|
||||
class UploadFileModel {
|
||||
constructor(additionalMetadata, file) {
|
||||
const validationErrors = Model.validateModel(UploadFileModel, {
|
||||
additionalMetadata, file,
|
||||
});
|
||||
if (validationErrors.length === 0) {
|
||||
this.additionalMetadata = additionalMetadata;
|
||||
this.file = file;
|
||||
} else {
|
||||
throw ono('Tried to create an invalid UploadFileModel instance', { errors: validationErrors });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UploadFileModel.types = {
|
||||
additionalMetadata: 'string',
|
||||
file: 'string',
|
||||
};
|
||||
|
||||
UploadFileModel.required = [];
|
||||
|
||||
module.exports = UploadFileModel;
|
@ -0,0 +1,9 @@
|
||||
const CategoryModel = require('./Category');
|
||||
const PetModel = require('./Pet');
|
||||
const TagModel = require('./Tag');
|
||||
|
||||
module.exports = {
|
||||
CategoryModel,
|
||||
PetModel,
|
||||
TagModel,
|
||||
};
|
46
samples/server/petstore/nodejs-express-server/package.json
Normal file
46
samples/server/petstore/nodejs-express-server/package.json
Normal file
@ -0,0 +1,46 @@
|
||||
{
|
||||
"name": "openapi-petstore",
|
||||
"version": "1.0.0",
|
||||
"description": "This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters.",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"prestart": "npm install",
|
||||
"start": "node index.js"
|
||||
},
|
||||
"keywords": [
|
||||
"openapi-generator",
|
||||
"openapi"
|
||||
],
|
||||
"license": "Unlicense",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"body-parser": "^1.19.0",
|
||||
"connect": "^3.2.0",
|
||||
"cookie-parser": "^1.4.4",
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.16.4",
|
||||
"express-openapi-validator": "^1.0.0",
|
||||
"js-yaml": "^3.3.0",
|
||||
"jstoxml": "^1.5.0",
|
||||
"ono": "^5.0.1",
|
||||
"openapi-sampler": "^1.0.0-beta.15",
|
||||
"swagger-express-middleware": "^2.0.2",
|
||||
"swagger-tools": "^0.10.4",
|
||||
"swagger-ui-express": "^4.0.2",
|
||||
"winston": "^3.2.1",
|
||||
"yamljs": "^0.3.0",
|
||||
"mocha": "^6.1.4",
|
||||
"axios": "^0.19.0",
|
||||
"chai": "^4.2.0",
|
||||
"chai-as-promised": "^7.1.1",
|
||||
"eslint": "^5.16.0",
|
||||
"eslint-config-airbnb-base": "^13.1.0",
|
||||
"eslint-plugin-import": "^2.17.2",
|
||||
"form-data": "^2.3.3"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"env": {
|
||||
"node": true
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,184 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
const Service = require('./Service');
|
||||
|
||||
class PetService {
|
||||
|
||||
/**
|
||||
* Add a new pet to the store
|
||||
*
|
||||
* body Pet Pet object that needs to be added to the store
|
||||
* no response value expected for this operation
|
||||
**/
|
||||
static addPet({ body }) {
|
||||
return new Promise(
|
||||
async (resolve) => {
|
||||
try {
|
||||
resolve(Service.successResponse(''));
|
||||
} catch (e) {
|
||||
resolve(Service.rejectResponse(
|
||||
e.message || 'Invalid input',
|
||||
e.status || 405,
|
||||
));
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a pet
|
||||
*
|
||||
* petId Long Pet id to delete
|
||||
* apiUnderscorekey String (optional)
|
||||
* no response value expected for this operation
|
||||
**/
|
||||
static deletePet({ petId, apiUnderscorekey }) {
|
||||
return new Promise(
|
||||
async (resolve) => {
|
||||
try {
|
||||
resolve(Service.successResponse(''));
|
||||
} catch (e) {
|
||||
resolve(Service.rejectResponse(
|
||||
e.message || 'Invalid input',
|
||||
e.status || 405,
|
||||
));
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds Pets by status
|
||||
* Multiple status values can be provided with comma separated strings
|
||||
*
|
||||
* status List Status values that need to be considered for filter
|
||||
* returns List
|
||||
**/
|
||||
static findPetsByStatus({ status }) {
|
||||
return new Promise(
|
||||
async (resolve) => {
|
||||
try {
|
||||
resolve(Service.successResponse(''));
|
||||
} catch (e) {
|
||||
resolve(Service.rejectResponse(
|
||||
e.message || 'Invalid input',
|
||||
e.status || 405,
|
||||
));
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds Pets by tags
|
||||
* Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.
|
||||
*
|
||||
* tags List Tags to filter by
|
||||
* returns List
|
||||
**/
|
||||
static findPetsByTags({ tags }) {
|
||||
return new Promise(
|
||||
async (resolve) => {
|
||||
try {
|
||||
resolve(Service.successResponse(''));
|
||||
} catch (e) {
|
||||
resolve(Service.rejectResponse(
|
||||
e.message || 'Invalid input',
|
||||
e.status || 405,
|
||||
));
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find pet by ID
|
||||
* Returns a single pet
|
||||
*
|
||||
* petId Long ID of pet to return
|
||||
* returns Pet
|
||||
**/
|
||||
static getPetById({ petId }) {
|
||||
return new Promise(
|
||||
async (resolve) => {
|
||||
try {
|
||||
resolve(Service.successResponse(''));
|
||||
} catch (e) {
|
||||
resolve(Service.rejectResponse(
|
||||
e.message || 'Invalid input',
|
||||
e.status || 405,
|
||||
));
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an existing pet
|
||||
*
|
||||
* body Pet Pet object that needs to be added to the store
|
||||
* no response value expected for this operation
|
||||
**/
|
||||
static updatePet({ body }) {
|
||||
return new Promise(
|
||||
async (resolve) => {
|
||||
try {
|
||||
resolve(Service.successResponse(''));
|
||||
} catch (e) {
|
||||
resolve(Service.rejectResponse(
|
||||
e.message || 'Invalid input',
|
||||
e.status || 405,
|
||||
));
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a pet in the store with form data
|
||||
*
|
||||
* petId Long ID of pet that needs to be updated
|
||||
* name String Updated name of the pet (optional)
|
||||
* status String Updated status of the pet (optional)
|
||||
* no response value expected for this operation
|
||||
**/
|
||||
static updatePetWithForm({ petId, name, status }) {
|
||||
return new Promise(
|
||||
async (resolve) => {
|
||||
try {
|
||||
resolve(Service.successResponse(''));
|
||||
} catch (e) {
|
||||
resolve(Service.rejectResponse(
|
||||
e.message || 'Invalid input',
|
||||
e.status || 405,
|
||||
));
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* uploads an image
|
||||
*
|
||||
* petId Long ID of pet to update
|
||||
* additionalMetadata String Additional data to pass to server (optional)
|
||||
* file File file to upload (optional)
|
||||
* returns ApiResponse
|
||||
**/
|
||||
static uploadFile({ petId, additionalMetadata, file }) {
|
||||
return new Promise(
|
||||
async (resolve) => {
|
||||
try {
|
||||
resolve(Service.successResponse(''));
|
||||
} catch (e) {
|
||||
resolve(Service.rejectResponse(
|
||||
e.message || 'Invalid input',
|
||||
e.status || 405,
|
||||
));
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = PetService;
|
@ -0,0 +1,11 @@
|
||||
class Service {
|
||||
static rejectResponse(error, code = 500) {
|
||||
return { error, code };
|
||||
}
|
||||
|
||||
static successResponse(payload, code = 200) {
|
||||
return { payload, code };
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Service;
|
@ -0,0 +1,94 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
const Service = require('./Service');
|
||||
|
||||
class StoreService {
|
||||
|
||||
/**
|
||||
* Delete purchase order by ID
|
||||
* For valid response try integer IDs with value < 1000. Anything above 1000 or nonintegers will generate API errors
|
||||
*
|
||||
* orderId String ID of the order that needs to be deleted
|
||||
* no response value expected for this operation
|
||||
**/
|
||||
static deleteOrder({ orderId }) {
|
||||
return new Promise(
|
||||
async (resolve) => {
|
||||
try {
|
||||
resolve(Service.successResponse(''));
|
||||
} catch (e) {
|
||||
resolve(Service.rejectResponse(
|
||||
e.message || 'Invalid input',
|
||||
e.status || 405,
|
||||
));
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns pet inventories by status
|
||||
* Returns a map of status codes to quantities
|
||||
*
|
||||
* returns Map
|
||||
**/
|
||||
static getInventory() {
|
||||
return new Promise(
|
||||
async (resolve) => {
|
||||
try {
|
||||
resolve(Service.successResponse(''));
|
||||
} catch (e) {
|
||||
resolve(Service.rejectResponse(
|
||||
e.message || 'Invalid input',
|
||||
e.status || 405,
|
||||
));
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find purchase order by ID
|
||||
* For valid response try integer IDs with value <= 5 or > 10. Other values will generated exceptions
|
||||
*
|
||||
* orderId Long ID of pet that needs to be fetched
|
||||
* returns Order
|
||||
**/
|
||||
static getOrderById({ orderId }) {
|
||||
return new Promise(
|
||||
async (resolve) => {
|
||||
try {
|
||||
resolve(Service.successResponse(''));
|
||||
} catch (e) {
|
||||
resolve(Service.rejectResponse(
|
||||
e.message || 'Invalid input',
|
||||
e.status || 405,
|
||||
));
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Place an order for a pet
|
||||
*
|
||||
* body Order order placed for purchasing the pet
|
||||
* returns Order
|
||||
**/
|
||||
static placeOrder({ body }) {
|
||||
return new Promise(
|
||||
async (resolve) => {
|
||||
try {
|
||||
resolve(Service.successResponse(''));
|
||||
} catch (e) {
|
||||
resolve(Service.rejectResponse(
|
||||
e.message || 'Invalid input',
|
||||
e.status || 405,
|
||||
));
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = StoreService;
|
@ -0,0 +1,180 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
const Service = require('./Service');
|
||||
|
||||
class UserService {
|
||||
|
||||
/**
|
||||
* Create user
|
||||
* This can only be done by the logged in user.
|
||||
*
|
||||
* body User Created user object
|
||||
* no response value expected for this operation
|
||||
**/
|
||||
static createUser({ body }) {
|
||||
return new Promise(
|
||||
async (resolve) => {
|
||||
try {
|
||||
resolve(Service.successResponse(''));
|
||||
} catch (e) {
|
||||
resolve(Service.rejectResponse(
|
||||
e.message || 'Invalid input',
|
||||
e.status || 405,
|
||||
));
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates list of users with given input array
|
||||
*
|
||||
* body List List of user object
|
||||
* no response value expected for this operation
|
||||
**/
|
||||
static createUsersWithArrayInput({ body }) {
|
||||
return new Promise(
|
||||
async (resolve) => {
|
||||
try {
|
||||
resolve(Service.successResponse(''));
|
||||
} catch (e) {
|
||||
resolve(Service.rejectResponse(
|
||||
e.message || 'Invalid input',
|
||||
e.status || 405,
|
||||
));
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates list of users with given input array
|
||||
*
|
||||
* body List List of user object
|
||||
* no response value expected for this operation
|
||||
**/
|
||||
static createUsersWithListInput({ body }) {
|
||||
return new Promise(
|
||||
async (resolve) => {
|
||||
try {
|
||||
resolve(Service.successResponse(''));
|
||||
} catch (e) {
|
||||
resolve(Service.rejectResponse(
|
||||
e.message || 'Invalid input',
|
||||
e.status || 405,
|
||||
));
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete user
|
||||
* This can only be done by the logged in user.
|
||||
*
|
||||
* username String The name that needs to be deleted
|
||||
* no response value expected for this operation
|
||||
**/
|
||||
static deleteUser({ username }) {
|
||||
return new Promise(
|
||||
async (resolve) => {
|
||||
try {
|
||||
resolve(Service.successResponse(''));
|
||||
} catch (e) {
|
||||
resolve(Service.rejectResponse(
|
||||
e.message || 'Invalid input',
|
||||
e.status || 405,
|
||||
));
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user by user name
|
||||
*
|
||||
* username String The name that needs to be fetched. Use user1 for testing.
|
||||
* returns User
|
||||
**/
|
||||
static getUserByName({ username }) {
|
||||
return new Promise(
|
||||
async (resolve) => {
|
||||
try {
|
||||
resolve(Service.successResponse(''));
|
||||
} catch (e) {
|
||||
resolve(Service.rejectResponse(
|
||||
e.message || 'Invalid input',
|
||||
e.status || 405,
|
||||
));
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs user into the system
|
||||
*
|
||||
* username String The user name for login
|
||||
* password String The password for login in clear text
|
||||
* returns String
|
||||
**/
|
||||
static loginUser({ username, password }) {
|
||||
return new Promise(
|
||||
async (resolve) => {
|
||||
try {
|
||||
resolve(Service.successResponse(''));
|
||||
} catch (e) {
|
||||
resolve(Service.rejectResponse(
|
||||
e.message || 'Invalid input',
|
||||
e.status || 405,
|
||||
));
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs out current logged in user session
|
||||
*
|
||||
* no response value expected for this operation
|
||||
**/
|
||||
static logoutUser() {
|
||||
return new Promise(
|
||||
async (resolve) => {
|
||||
try {
|
||||
resolve(Service.successResponse(''));
|
||||
} catch (e) {
|
||||
resolve(Service.rejectResponse(
|
||||
e.message || 'Invalid input',
|
||||
e.status || 405,
|
||||
));
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updated user
|
||||
* This can only be done by the logged in user.
|
||||
*
|
||||
* username String name that need to be deleted
|
||||
* body User Updated user object
|
||||
* no response value expected for this operation
|
||||
**/
|
||||
static updateUser({ username, body }) {
|
||||
return new Promise(
|
||||
async (resolve) => {
|
||||
try {
|
||||
resolve(Service.successResponse(''));
|
||||
} catch (e) {
|
||||
resolve(Service.rejectResponse(
|
||||
e.message || 'Invalid input',
|
||||
e.status || 405,
|
||||
));
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = UserService;
|
@ -0,0 +1,9 @@
|
||||
const PetService = require('./PetService');
|
||||
const StoreService = require('./StoreService');
|
||||
const UserService = require('./UserService');
|
||||
|
||||
module.exports = {
|
||||
PetService,
|
||||
StoreService,
|
||||
UserService,
|
||||
};
|
@ -0,0 +1,46 @@
|
||||
const {
|
||||
describe, before, after, it,
|
||||
} = require('mocha');
|
||||
const assert = require('assert').strict;
|
||||
const chai = require('chai');
|
||||
const chaiAsPromised = require('chai-as-promised');
|
||||
const axios = require('axios');
|
||||
const logger = require('./logger');
|
||||
const config = require('./config');
|
||||
const ExpressServer = require('../expressServer');
|
||||
|
||||
const app = new ExpressServer(config.URL_PORT, config.OPENAPI_YAML);
|
||||
chai.use(chaiAsPromised);
|
||||
chai.should();
|
||||
|
||||
describe('Test endpoints that are not part of the openapi.yaml.', () => {
|
||||
before(async () => {
|
||||
try {
|
||||
await app.launch();
|
||||
logger.info('express server launched\n');
|
||||
} catch (error) {
|
||||
logger.info(error);
|
||||
await app.close();
|
||||
throw (error);
|
||||
}
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await app.close()
|
||||
.catch(error => logger.error(error));
|
||||
logger.error('express server closed');
|
||||
});
|
||||
|
||||
|
||||
it('should confirm that requesting the openapi.yaml returns a successful 200 response', async () => {
|
||||
const pathToCall = `${config.URL_PATH}:${config.URL_PORT}/spec/openapi.yaml`;
|
||||
try {
|
||||
const openapiResponse = await axios.get(pathToCall);
|
||||
openapiResponse.should.have.property('status');
|
||||
openapiResponse.status.should.equal(200);
|
||||
} catch (e) {
|
||||
logger.error(`failed to call ${pathToCall}`);
|
||||
assert.fail(`Failed to call openapi.yaml - ${e.message}`);
|
||||
}
|
||||
});
|
||||
});
|
@ -0,0 +1,12 @@
|
||||
const path = require('path');
|
||||
|
||||
const config = {
|
||||
ROOT_DIR: path.join(__dirname, '../'),
|
||||
URL_PORT: 3009,
|
||||
URL_PATH: 'http://localhost',
|
||||
BASE_VERSION: 'v2',
|
||||
};
|
||||
config.OPENAPI_YAML = path.join(config.ROOT_DIR, 'api', 'openapi.yaml');
|
||||
config.FULL_PATH = `${config.URL_PATH}:${config.URL_PORT}/${config.BASE_VERSION}`;
|
||||
|
||||
module.exports = config;
|
@ -0,0 +1,14 @@
|
||||
const winston = require('winston');
|
||||
|
||||
const logger = winston.createLogger({
|
||||
level: 'info',
|
||||
format: winston.format.json(),
|
||||
defaultMeta: { service: 'test-service' },
|
||||
transports: [
|
||||
new winston.transports.File({ filename: 'error.log', level: 'error' }),
|
||||
new winston.transports.File({ filename: 'combined.log' }),
|
||||
new winston.transports.Console({ format: winston.format.simple() }),
|
||||
],
|
||||
});
|
||||
|
||||
module.exports = logger;
|
@ -0,0 +1,147 @@
|
||||
/**
|
||||
* The purpose of these tests is to confirm that every path in the openapi spec, if built properly,
|
||||
* returns a valid 200, with a simple text response.
|
||||
* The codeGen will generate a response string including the name of the operation that was called.
|
||||
* These tests confirm that the codeGen worked as expected.
|
||||
* Once we start adding our own business logic, these
|
||||
* tests will fail. It is recommended to keep these tests updated with the code changes.
|
||||
*/
|
||||
const {
|
||||
describe, before, after, it,
|
||||
} = require('mocha');
|
||||
const assert = require('assert').strict;
|
||||
const chai = require('chai');
|
||||
const chaiAsPromised = require('chai-as-promised');
|
||||
const axios = require('axios');
|
||||
const yamljs = require('yamljs');
|
||||
const openApiSampler = require('openapi-sampler');
|
||||
const jstoxml = require('jstoxml');
|
||||
const logger = require('./logger');
|
||||
const config = require('./config');
|
||||
const ExpressServer = require('../expressServer');
|
||||
|
||||
const app = new ExpressServer(config.URL_PORT, config.OPENAPI_YAML);
|
||||
chai.use(chaiAsPromised);
|
||||
chai.should();
|
||||
|
||||
|
||||
const pathPrefix = `${config.URL_PATH}:${config.URL_PORT}/api/v2`;
|
||||
const spec = yamljs.load(config.OPENAPI_YAML);
|
||||
|
||||
const parseParameters = (originalPath, schemaParameters) => {
|
||||
let path = originalPath;
|
||||
const headers = {};
|
||||
const queryParams = [];
|
||||
schemaParameters.forEach((parameter) => {
|
||||
const parameterValue = parameter.example || openApiSampler.sample(parameter.schema);
|
||||
switch (parameter.in) {
|
||||
case 'header':
|
||||
headers[parameter.name] = parameterValue;
|
||||
break;
|
||||
case 'path':
|
||||
path = path.replace(`{${parameter.name}}`, parameterValue);
|
||||
break;
|
||||
case 'query':
|
||||
queryParams.push(`${parameter.name}=${parameterValue}`);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
return { path, headers, queryString: queryParams.join('&') };
|
||||
};
|
||||
|
||||
const buildRequestObject = (pathEndpoint, method, operationObject, requestsArray) => {
|
||||
logger.info(`method: ${method}`);
|
||||
let headers = {};
|
||||
let requestBody = {};
|
||||
let queryString = '';
|
||||
let path = pathEndpoint;
|
||||
if (operationObject.parameters !== undefined) {
|
||||
logger.info('this is a request with parameters');
|
||||
({ path, headers, queryString } = parseParameters(pathEndpoint, operationObject.parameters));
|
||||
if (queryString.length > 0) {
|
||||
path += `?${queryString}`;
|
||||
}
|
||||
Object.entries(headers).forEach(([headerName, headerValue]) => {
|
||||
headers[headerName] = headerValue;
|
||||
});
|
||||
}
|
||||
if (operationObject.requestBody !== undefined) {
|
||||
logger.info('This is a request with a body');
|
||||
const content = Object.entries(operationObject.requestBody.content);
|
||||
content.forEach(([contentType, contentObject]) => {
|
||||
requestBody = openApiSampler.sample(contentObject.schema, {}, spec);
|
||||
let requestXML;
|
||||
if (contentType === 'application/xml') {
|
||||
requestXML = jstoxml.toXML(requestBody);
|
||||
}
|
||||
headers['Content-Type'] = contentType;
|
||||
requestsArray.push({
|
||||
method,
|
||||
path,
|
||||
body: requestXML || requestBody,
|
||||
headers,
|
||||
});
|
||||
});
|
||||
} else {
|
||||
requestsArray.push({
|
||||
method,
|
||||
path,
|
||||
headers,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const getApiRequestsData = (apiSchema) => {
|
||||
const requestsArray = [];
|
||||
Object.entries(apiSchema.paths).forEach(([pathEndpoint, pathObject]) => {
|
||||
logger.info(`adding path: ${pathPrefix}${pathEndpoint} to testing array`);
|
||||
Object.entries(pathObject).forEach(([operationMethod, operationObject]) => {
|
||||
buildRequestObject(pathEndpoint, operationMethod, operationObject, requestsArray);
|
||||
});
|
||||
});
|
||||
return requestsArray;
|
||||
};
|
||||
|
||||
describe('API tests, checking that the codegen generated code that allows all paths specified in schema to work', () => {
|
||||
before(async () => {
|
||||
try {
|
||||
await app.launch();
|
||||
logger.info('express server launched\n');
|
||||
} catch (error) {
|
||||
logger.info(error);
|
||||
await app.close();
|
||||
throw (error);
|
||||
}
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await app.close()
|
||||
.catch(error => logger.error(error));
|
||||
logger.error('express server closed');
|
||||
});
|
||||
|
||||
const requestsArray = getApiRequestsData(spec);
|
||||
requestsArray.forEach((requestObject) => {
|
||||
it(`should run ${requestObject.method.toUpperCase()} request to ${requestObject.path} and return healthy 200`, async () => {
|
||||
try {
|
||||
const {
|
||||
method, path, body, headers,
|
||||
} = requestObject;
|
||||
const url = `${pathPrefix}${path}`;
|
||||
logger.info(`testing ${method.toUpperCase()} call to ${url}. encoding: ${headers['Content-Type']}`);
|
||||
const response = await axios({
|
||||
method,
|
||||
url,
|
||||
data: body,
|
||||
headers,
|
||||
});
|
||||
response.should.have.property('status');
|
||||
response.status.should.equal(200);
|
||||
} catch (e) {
|
||||
assert.fail(e.message);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
@ -0,0 +1,57 @@
|
||||
const {
|
||||
describe, before, after, it,
|
||||
} = require('mocha');
|
||||
const chai = require('chai');
|
||||
const chaiAsPromised = require('chai-as-promised');
|
||||
const { get } = require('axios');
|
||||
|
||||
const logger = require('./logger');
|
||||
const config = require('./config');
|
||||
const ExpressServer = require('../expressServer');
|
||||
|
||||
const app = new ExpressServer(config.URL_PORT, config.OPENAPI_YAML);
|
||||
chai.use(chaiAsPromised);
|
||||
chai.should();
|
||||
|
||||
describe('Server tests, checking launch, terminate, and various error messages', () => {
|
||||
before(async () => {
|
||||
try {
|
||||
await app.launch();
|
||||
logger.info('express server launched\n');
|
||||
} catch (error) {
|
||||
logger.info(error);
|
||||
await app.close();
|
||||
throw (error);
|
||||
}
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await app.close()
|
||||
.catch(error => logger.error(error));
|
||||
logger.error('express server closed');
|
||||
});
|
||||
|
||||
it('should launch express server successfully', async () => {
|
||||
const indexResponse = await get(`${config.URL_PATH}:${config.URL_PORT}/`);
|
||||
indexResponse.status.should.equal(200, 'Expecting a call to root directory of server to return 200 code');
|
||||
});
|
||||
|
||||
it('should fail with a 404 on non-existing page', async () => {
|
||||
get(`${config.FULL_PATH}/someRandomPage`)
|
||||
.then(response => response.status.should.equal(404, 'expecting a 404 on a non-existing page request'))
|
||||
.catch((responseError) => {
|
||||
responseError.response.status.should.not.equal(undefined);
|
||||
responseError.response.status.should.equal(404, 'expecting to receive a 404 on requesting a non-existing page');
|
||||
});
|
||||
});
|
||||
|
||||
it('should load api-doc', async () => {
|
||||
try {
|
||||
const response = await get(`http://localhost:${config.URL_PORT}/api-docs`);
|
||||
response.status.should.equal(200, 'Expecting 200');
|
||||
} catch (e) {
|
||||
console.log(e.message);
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
});
|
@ -0,0 +1,29 @@
|
||||
const {
|
||||
describe, it,
|
||||
} = require('mocha');
|
||||
const chai = require('chai');
|
||||
const chaiAsPromised = require('chai-as-promised');
|
||||
const Pet = require('../models/Pet');
|
||||
const logger = require('./logger');
|
||||
|
||||
chai.use(chaiAsPromised);
|
||||
chai.should();
|
||||
|
||||
describe('Model tests, checking that they are created correctly, and throw the expected message when not', () => {
|
||||
it('Should create a model for Pet, according to openapi.yaml definition, and throw errors if fails',
|
||||
async () => {
|
||||
const photoUrls = ['petPhoto1.jpg', 'petPhoto2.jpg', 'petPhoto3.jpg'];
|
||||
const name = 'petName';
|
||||
const id = 0;
|
||||
const category = { id: 1, name: 'categoryName' };
|
||||
const tags = [{ id: 2, name: 'tagName1' }, { id: 3, name: 'tagName2' }];
|
||||
const status = 'available';
|
||||
const pet = new Pet(photoUrls, name, id, tags, status, category);
|
||||
pet.id.should.equal(id);
|
||||
pet.name.should.equal(name);
|
||||
pet.category.id.should.equal(category.id);
|
||||
pet.category.name.should.equal(category.name);
|
||||
pet.tags.length.should.equal(tags.length);
|
||||
pet.status.should.equal(status);
|
||||
});
|
||||
});
|
@ -0,0 +1,67 @@
|
||||
const logger = require('../logger');
|
||||
const controllers = require('../controllers');
|
||||
const Services = require('../services');
|
||||
|
||||
function handleError(err, request, response, next) {
|
||||
logger.error(err);
|
||||
const code = err.code || 400;
|
||||
response.status(code);
|
||||
response.error = err;
|
||||
next(JSON.stringify({
|
||||
code,
|
||||
error: err,
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* The purpose of this route is to collect the request variables as defined in the
|
||||
* OpenAPI document and pass them to the handling controller as another Express
|
||||
* middleware. All parameters are collected in the requet.swagger.values key-value object
|
||||
*
|
||||
* The assumption is that security handlers have already verified and allowed access
|
||||
* to this path. If the business-logic of a particular path is dependant on authentication
|
||||
* parameters (e.g. scope checking) - it is recommended to define the authentication header
|
||||
* as one of the parameters expected in the OpenAPI/Swagger document.
|
||||
*
|
||||
* Requests made to paths that are not in the OpernAPI scope
|
||||
* are passed on to the next middleware handler.
|
||||
* @returns {Function}
|
||||
*/
|
||||
function openApiRouter() {
|
||||
return async (request, response, next) => {
|
||||
try {
|
||||
/**
|
||||
* This middleware runs after a previous process have applied an openapi object
|
||||
* to the request.
|
||||
* If none was applied This is because the path requested is not in the schema.
|
||||
* If there's no openapi object, we have nothing to do, and pass on to next middleware.
|
||||
*/
|
||||
if (request.openapi === undefined
|
||||
|| request.openapi.schema === undefined
|
||||
) {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
// request.swagger.paramValues = {};
|
||||
// request.swagger.params.forEach((param) => {
|
||||
// request.swagger.paramValues[param.name] = getValueFromRequest(request, param);
|
||||
// });
|
||||
const controllerName = request.openapi.schema['x-openapi-router-controller'];
|
||||
const serviceName = request.openapi.schema['x-openapi-router-service'];
|
||||
if (!controllers[controllerName] || controllers[controllerName] === undefined) {
|
||||
handleError(`request sent to controller '${controllerName}' which has not been defined`,
|
||||
request, response, next);
|
||||
} else {
|
||||
const apiController = new controllers[controllerName](Services[serviceName]);
|
||||
const controllerOperation = request.openapi.schema.operationId;
|
||||
await apiController[controllerOperation](request, response, next);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
const err = { code: 500, error: error.message };
|
||||
handleError(err, request, response, next);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = openApiRouter;
|
Loading…
x
Reference in New Issue
Block a user