@conqa/serverless-openapi-documentation
Advanced tools
Comparing version 1.0.4 to 1.1.0
@@ -8,5 +8,41 @@ # Changelog | ||
## [Unreleased] | ||
[Unreleased]: https://github.com/conqa/serverless-openapi-documentation/compare/v1.0.4...HEAD | ||
## [1.1.0] - 2019-05-09 | ||
### Added | ||
- Support for securitySchemes and security #15 | ||
- Support for servers #18 | ||
- Support for schemas included by path with all $refs merged into components #7 | ||
### Changed | ||
- Convert project from Yarn to npm #5 | ||
- Switch to eslint #8 | ||
- Update Readme #19 | ||
## [1.0.4] - 2019-05-07 | ||
### Fixed | ||
- Fix issue with last release where the package was published from the root directory, not the build directory | ||
## [1.0.3] - 2019-05-07 | ||
### Fixed | ||
- Fix for requestBody and parameters #6 | ||
## [1.0.2] - 2019-05-03 | ||
### Changed | ||
- test tag publish | ||
## [1.0.1] - 2019-05-02 | ||
### Changed | ||
- test tag publish | ||
## [1.0.0] - 2019-05-02 | ||
### Changed | ||
- inital release after forking from temando/serverless-openapi-documentation | ||
[Unreleased]: https://github.com/conqa/serverless-openapi-documentation/compare/v1.1.0...HEAD | ||
[1.1.0]: https://github.com/conqa/serverless-openapi-documentation/compare/v1.0.4...v1.1.0 | ||
[1.0.4]: https://github.com/conqa/serverless-openapi-documentation/compare/v1.0.3...v1.0.4 | ||
[1.0.3]: https://github.com/conqa/serverless-openapi-documentation/tree/v1.0.3 | ||
[1.0.3]: https://github.com/conqa/serverless-openapi-documentation/compare/v1.0.2...v1.0.3 | ||
[1.0.2]: https://github.com/conqa/serverless-openapi-documentation/compare/v1.0.1...v1.0.2 | ||
[1.0.1]: https://github.com/conqa/serverless-openapi-documentation/compare/v1.0.0...v1.0.1 | ||
[1.0.0]: https://github.com/conqa/serverless-openapi-documentation/tree/v1.0.0 |
@@ -1,17 +0,17 @@ | ||
import { IDefinition, IDefinitionConfig, IServerlessFunctionConfig } from './types'; | ||
import { Definition, DefinitionConfig, ServerlessFunctionConfig } from "./types"; | ||
export declare class DefinitionGenerator { | ||
version: string; | ||
definition: IDefinition; | ||
config: IDefinitionConfig; | ||
definition: Definition; | ||
config: DefinitionConfig; | ||
private root; | ||
/** | ||
* Constructor | ||
* @param serviceDescriptor IServiceDescription | ||
*/ | ||
constructor(config: IDefinitionConfig); | ||
parse(): this; | ||
constructor(config: DefinitionConfig, root: string); | ||
parse(): Promise<this>; | ||
validate(): { | ||
valid: boolean; | ||
context: string[]; | ||
warnings: any[]; | ||
error?: any[]; | ||
context: Array<string>; | ||
warnings: Array<any>; | ||
error?: Array<any>; | ||
}; | ||
@@ -22,14 +22,4 @@ /** | ||
*/ | ||
readFunctions(config: IServerlessFunctionConfig[]): void; | ||
readFunctions(config: Array<ServerlessFunctionConfig>): void; | ||
/** | ||
* Cleans schema objects to make them OpenAPI compatible | ||
* @param schema JSON Schema Object | ||
*/ | ||
private cleanSchema(schema); | ||
/** | ||
* Walks through the schema object recursively and updates references to point to openapi's components | ||
* @param schema JSON Schema Object | ||
*/ | ||
private updateReferences(schema); | ||
/** | ||
* Generate Operation objects from the Serverless Config. | ||
@@ -41,3 +31,3 @@ * | ||
*/ | ||
private getOperationFromConfig(funcName, documentationConfig); | ||
private getOperationFromConfig; | ||
/** | ||
@@ -47,3 +37,3 @@ * Derives Path, Query and Request header parameters from Serverless documentation | ||
*/ | ||
private getParametersFromConfig(documentationConfig); | ||
private getParametersFromConfig; | ||
/** | ||
@@ -53,4 +43,4 @@ * Derives request body schemas from event documentation configuration | ||
*/ | ||
private getRequestBodiesFromConfig(documentationConfig); | ||
private attachExamples(target, config); | ||
private getRequestBodiesFromConfig; | ||
private attachExamples; | ||
/** | ||
@@ -60,5 +50,5 @@ * Gets response bodies from documentation config | ||
*/ | ||
private getResponsesFromConfig(documentationConfig); | ||
private getResponseContent(response); | ||
private getHttpEvents(funcConfig); | ||
private getResponsesFromConfig; | ||
private getResponseContent; | ||
private getHttpEvents; | ||
} |
"use strict"; | ||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
return new (P || (P = Promise))(function (resolve, reject) { | ||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } | ||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } | ||
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } | ||
step((generator = generator.apply(thisArg, _arguments || [])).next()); | ||
}); | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const _ = require("lodash"); | ||
// tslint:disable-next-line no-submodule-imports | ||
const validate_1 = require("swagger2openapi/validate"); | ||
const uuid = require("uuid"); | ||
const parse_1 = require("./parse"); | ||
const utils_1 = require("./utils"); | ||
@@ -10,41 +20,37 @@ class DefinitionGenerator { | ||
* Constructor | ||
* @param serviceDescriptor IServiceDescription | ||
*/ | ||
constructor(config) { | ||
constructor(config, root) { | ||
// The OpenAPI version we currently validate against | ||
this.version = '3.0.0'; | ||
this.version = "3.0.0"; | ||
// Base configuration object | ||
this.definition = { | ||
openapi: this.version, | ||
components: {}, | ||
components: {} | ||
}; | ||
this.config = utils_1.clone(config); | ||
this.config = _.cloneDeep(config); | ||
this.root = root; | ||
} | ||
parse() { | ||
const { title = '', description = '', version = uuid.v4(), models, } = this.config; | ||
utils_1.merge(this.definition, { | ||
openapi: this.version, | ||
info: { title, description, version }, | ||
paths: {}, | ||
components: { | ||
schemas: {}, | ||
securitySchemes: {}, | ||
}, | ||
}); | ||
if (utils_1.isIterable(models)) { | ||
for (const model of models) { | ||
if (!model.schema) { | ||
continue; | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const { title = "", description = "", version = uuid.v4(), models, security, securitySchemes, servers } = this.config; | ||
_.merge(this.definition, { | ||
openapi: this.version, | ||
info: { title, description, version }, | ||
paths: {}, | ||
components: { | ||
schemas: {} | ||
} | ||
for (const definitionName of Object.keys(model.schema.definitions || {})) { | ||
const definition = model.schema.definitions[definitionName]; | ||
if (typeof definition !== 'boolean') { | ||
this.definition.components.schemas[definitionName] = this.cleanSchema(this.updateReferences(definition)); | ||
} | ||
} | ||
const schemaWithoutDefinitions = utils_1.omit(model.schema, ['definitions']); | ||
this.definition.components.schemas[model.name] = this.cleanSchema(this.updateReferences(schemaWithoutDefinitions)); | ||
}); | ||
if (security) { | ||
this.definition.security = security; | ||
} | ||
} | ||
return this; | ||
if (securitySchemes) { | ||
this.definition.components.securitySchemes = securitySchemes; | ||
} | ||
if (servers) { | ||
this.definition.servers = servers; | ||
} | ||
this.definition.components.schemas = yield parse_1.parseModels(models, this.root); | ||
return this; | ||
}); | ||
} | ||
@@ -75,7 +81,7 @@ validate() { | ||
[`/${httpEventConfig.path}`]: { | ||
[httpEventConfig.method.toLowerCase()]: this.getOperationFromConfig(funcConfig._functionName, httpEventConfig.documentation), | ||
}, | ||
[httpEventConfig.method.toLowerCase()]: this.getOperationFromConfig(funcConfig._functionName, httpEventConfig.documentation) | ||
} | ||
}; | ||
// merge path configuration into main configuration | ||
utils_1.merge(this.definition.paths, pathConfig); | ||
_.merge(this.definition.paths, pathConfig); | ||
} | ||
@@ -86,35 +92,2 @@ } | ||
/** | ||
* Cleans schema objects to make them OpenAPI compatible | ||
* @param schema JSON Schema Object | ||
*/ | ||
cleanSchema(schema) { | ||
// Clone the schema for manipulation | ||
const cleanedSchema = utils_1.clone(schema); | ||
// Strip $schema from schemas | ||
if (cleanedSchema.$schema) { | ||
delete cleanedSchema.$schema; | ||
} | ||
// Return the cleaned schema | ||
return cleanedSchema; | ||
} | ||
/** | ||
* Walks through the schema object recursively and updates references to point to openapi's components | ||
* @param schema JSON Schema Object | ||
*/ | ||
updateReferences(schema) { | ||
const cloned = utils_1.clone(schema); | ||
if (cloned.$ref) { | ||
cloned.$ref = cloned.$ref.replace('#/definitions', '#/components/schemas'); | ||
} | ||
else { | ||
for (const key of Object.getOwnPropertyNames(cloned)) { | ||
const value = cloned[key]; | ||
if (typeof value === 'object') { | ||
cloned[key] = this.updateReferences(value); | ||
} | ||
} | ||
} | ||
return cloned; | ||
} | ||
/** | ||
* Generate Operation objects from the Serverless Config. | ||
@@ -128,3 +101,3 @@ * | ||
const operationObj = { | ||
operationId: funcName, | ||
operationId: funcName | ||
}; | ||
@@ -157,14 +130,14 @@ if (documentationConfig.summary) { | ||
// Build up parameters from configuration for each parameter type | ||
for (const type of ['path', 'query', 'header', 'cookie']) { | ||
for (const type of ["path", "query", "header", "cookie"]) { | ||
let paramBlock; | ||
if (type === 'path' && documentationConfig.pathParams) { | ||
if (type === "path" && documentationConfig.pathParams) { | ||
paramBlock = documentationConfig.pathParams; | ||
} | ||
else if (type === 'query' && documentationConfig.queryParams) { | ||
else if (type === "query" && documentationConfig.queryParams) { | ||
paramBlock = documentationConfig.queryParams; | ||
} | ||
else if (type === 'header' && documentationConfig.requestHeaders) { | ||
else if (type === "header" && documentationConfig.requestHeaders) { | ||
paramBlock = documentationConfig.requestHeaders; | ||
} | ||
else if (type === 'cookie' && documentationConfig.cookieParams) { | ||
else if (type === "cookie" && documentationConfig.cookieParams) { | ||
paramBlock = documentationConfig.cookieParams; | ||
@@ -180,26 +153,26 @@ } | ||
in: type, | ||
description: parameter.description || '', | ||
required: parameter.required || false, | ||
description: parameter.description || "", | ||
required: parameter.required || false // Note: all path parameters must be required | ||
}; | ||
// if type is path, then required must be true (@see OpenAPI 3.0-RC1) | ||
if (type === 'path') { | ||
if (type === "path") { | ||
parameterConfig.required = true; | ||
} | ||
else if (type === 'query') { | ||
else if (type === "query") { | ||
parameterConfig.allowEmptyValue = parameter.allowEmptyValue || false; // OpenAPI default is false | ||
if ('allowReserved' in parameter) { | ||
if ("allowReserved" in parameter) { | ||
parameterConfig.allowReserved = parameter.allowReserved || false; | ||
} | ||
} | ||
if ('deprecated' in parameter) { | ||
if ("deprecated" in parameter) { | ||
parameterConfig.deprecated = parameter.deprecated; | ||
} | ||
if ('style' in parameter) { | ||
if ("style" in parameter) { | ||
parameterConfig.style = parameter.style; | ||
parameterConfig.explode = parameter.explode | ||
? parameter.explode | ||
: parameter.style === 'form'; | ||
: parameter.style === "form"; | ||
} | ||
if (parameter.schema) { | ||
parameterConfig.schema = this.cleanSchema(parameter.schema); | ||
parameterConfig.schema = utils_1.cleanSchema(parameter.schema); | ||
} | ||
@@ -234,8 +207,10 @@ if (parameter.example) { | ||
// get schema reference information | ||
const requestModel = this.config.models.filter((model) => model.name === documentationConfig.requestModels[requestModelType]).pop(); | ||
const requestModel = this.config.models | ||
.filter(model => model.name === documentationConfig.requestModels[requestModelType]) | ||
.pop(); | ||
if (requestModel) { | ||
const reqModelConfig = { | ||
schema: { | ||
$ref: `#/components/schemas/${documentationConfig.requestModels[requestModelType]}`, | ||
}, | ||
$ref: `#/components/schemas/${documentationConfig.requestModels[requestModelType]}` | ||
} | ||
}; | ||
@@ -245,9 +220,11 @@ this.attachExamples(requestModel, reqModelConfig); | ||
content: { | ||
[requestModelType]: reqModelConfig, | ||
}, | ||
[requestModelType]: reqModelConfig | ||
} | ||
}; | ||
if (documentationConfig.requestBody && 'description' in documentationConfig.requestBody) { | ||
reqBodyConfig.description = documentationConfig.requestBody.description; | ||
if (documentationConfig.requestBody && | ||
"description" in documentationConfig.requestBody) { | ||
reqBodyConfig.description = | ||
documentationConfig.requestBody.description; | ||
} | ||
utils_1.merge(requestBodies, reqBodyConfig); | ||
_.merge(requestBodies, reqBodyConfig); | ||
} | ||
@@ -260,6 +237,6 @@ } | ||
if (target.examples && Array.isArray(target.examples)) { | ||
utils_1.merge(config, { examples: utils_1.clone(target.examples) }); | ||
_.merge(config, { examples: _.cloneDeep(target.examples) }); | ||
} | ||
else if (target.example) { | ||
utils_1.merge(config, { example: utils_1.clone(target.example) }); | ||
_.merge(config, { example: _.cloneDeep(target.example) }); | ||
} | ||
@@ -276,6 +253,6 @@ } | ||
const methodResponseConfig = { | ||
description: ((response.responseBody && 'description' in response.responseBody) | ||
description: response.responseBody && "description" in response.responseBody | ||
? response.responseBody.description | ||
: `Status ${response.statusCode} Response`), | ||
content: this.getResponseContent(response.responseModels), | ||
: `Status ${response.statusCode} Response`, | ||
content: this.getResponseContent(response.responseModels) | ||
}; | ||
@@ -286,11 +263,11 @@ if (response.responseHeaders) { | ||
methodResponseConfig.headers[header.name] = { | ||
description: header.description || `${header.name} header`, | ||
description: header.description || `${header.name} header` | ||
}; | ||
if (header.schema) { | ||
methodResponseConfig.headers[header.name].schema = this.cleanSchema(header.schema); | ||
methodResponseConfig.headers[header.name].schema = utils_1.cleanSchema(header.schema); | ||
} | ||
} | ||
} | ||
utils_1.merge(responses, { | ||
[response.statusCode]: methodResponseConfig, | ||
_.merge(responses, { | ||
[response.statusCode]: methodResponseConfig | ||
}); | ||
@@ -304,11 +281,11 @@ } | ||
for (const responseKey of Object.keys(response)) { | ||
const responseModel = this.config.models.find((model) => model.name === response[responseKey]); | ||
const responseModel = this.config.models.find(model => model.name === response[responseKey]); | ||
if (responseModel) { | ||
const resModelConfig = { | ||
schema: { | ||
$ref: `#/components/schemas/${response[responseKey]}`, | ||
}, | ||
$ref: `#/components/schemas/${response[responseKey]}` | ||
} | ||
}; | ||
this.attachExamples(responseModel, resModelConfig); | ||
utils_1.merge(content, { [responseKey]: resModelConfig }); | ||
_.merge(content, { [responseKey]: resModelConfig }); | ||
} | ||
@@ -319,3 +296,3 @@ } | ||
getHttpEvents(funcConfig) { | ||
return funcConfig.filter((event) => event.http ? true : false); | ||
return funcConfig.filter(event => (event.http ? true : false)); | ||
} | ||
@@ -322,0 +299,0 @@ } |
@@ -1,2 +0,2 @@ | ||
import { ServerlessOpenApiDocumentation } from './ServerlessOpenApiDocumentation'; | ||
import { ServerlessOpenApiDocumentation } from "./ServerlessOpenApiDocumentation"; | ||
export default ServerlessOpenApiDocumentation; |
{ | ||
"name": "@conqa/serverless-openapi-documentation", | ||
"version": "1.0.4", | ||
"version": "1.1.0", | ||
"description": "Serverless 1.0 plugin to generate OpenAPI V3 documentation from serverless configuration", | ||
"main": "index.js", | ||
"engines": { | ||
"node": ">=6.0.0", | ||
"npm": ">=3.8.6" | ||
}, | ||
"repository": { | ||
@@ -23,13 +27,14 @@ "type": "git", | ||
"scripts": { | ||
"test": "jest", | ||
"test:build": "jest -c '{}' build", | ||
"test:coverage": "jest --coverage", | ||
"lint": "tslint -p tsconfig.json --type-check -c tslint.json", | ||
"preversion": "yarn lint && yarn build && yarn test:build && changelog-verify CHANGELOG.md", | ||
"test": "jest -c ./jest.config.js", | ||
"test:build": "jest -c '{ \"testRegex\": \".spec.js$\"}' build", | ||
"test:coverage": "jest -c ./jest.config.js --coverage", | ||
"lint": "eslint --ext .ts,.tsx .", | ||
"lint:fix": "npm run lint -- --fix", | ||
"preversion": "npm run lint && npm run build && npm run test:build && changelog-verify CHANGELOG.md", | ||
"version": "version-changelog CHANGELOG.md && changelog-verify CHANGELOG.md && git add CHANGELOG.md", | ||
"release": "cd build && npm publish", | ||
"test:project": "cd test/project && yarn sls openapi generate", | ||
"test:project": "cd test/project && ./node_modules/.bin/sls openapi generate", | ||
"test:prepare": "scripts/prepareTests.bash", | ||
"build:link": "yarn build && cd build && yarn link", | ||
"build:watch": "yarn build && tsc --watch", | ||
"build:link": "npm run build && cd build && npm link", | ||
"build:watch": "npm run build && tsc --watch", | ||
"build": "scripts/build.bash" | ||
@@ -42,14 +47,21 @@ }, | ||
"@types/jest": "^20.0.2", | ||
"@types/js-yaml": "^3.5.31", | ||
"@types/js-yaml": "^3.12.1", | ||
"@types/json-schema": "^7.0.3", | ||
"@types/node": "^8.0.7", | ||
"@types/uuid": "^3.0.0", | ||
"@types/lodash": "^4.14.123", | ||
"@types/node": "^8.10.48", | ||
"@types/serverless": "^1.18.2", | ||
"@types/uuid": "^3.4.4", | ||
"@typescript-eslint/eslint-plugin": "^1.7.0", | ||
"@typescript-eslint/parser": "^1.7.0", | ||
"changelog-verify": "^1.0.4", | ||
"jest": "^20.0.4", | ||
"serverless": "^1.16.1", | ||
"ts-jest": "^20.0.6", | ||
"eslint": "^5.16.0", | ||
"eslint-config-prettier": "^4.2.0", | ||
"eslint-plugin-prettier": "^3.0.1", | ||
"jest": "^24.8.0", | ||
"openapi-types": "^1.3.4", | ||
"prettier": "^1.17.0", | ||
"serverless": "^1.41.1", | ||
"ts-jest": "^24.0.2", | ||
"ts-node": "^3.1.0", | ||
"tslint": "^5.4.3", | ||
"tslint-config-temando": "^1.1.4", | ||
"typescript": "^2.4.1", | ||
"typescript": "^3.4.5", | ||
"version-changelog": "^2.1.0" | ||
@@ -62,3 +74,4 @@ }, | ||
"js-yaml": "^3.8.4", | ||
"lutils": "^2.4.0", | ||
"json-schema-ref-parser": "^6.1.0", | ||
"lodash": "^4.17.11", | ||
"swagger2openapi": "^2.5.0", | ||
@@ -65,0 +78,0 @@ "uuid": "^3.1.0" |
# Serverless OpenAPI Documentation Plugin | ||
[![NPM](https://img.shields.io/npm/v/serverless-openapi-documentation.svg)](https://npmjs.org/packages/serverless-openapi-documentation/) | ||
[![Travis CI](https://img.shields.io/travis/temando/serverless-openapi-documentation.svg)](https://travis-ci.org/temando/serverless-openapi-documentation) | ||
[![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com) | ||
[![NPM](https://img.shields.io/npm/v/@conqa/serverless-openapi-documentation.svg)](https://npmjs.org/packages/@conqa/serverless-openapi-documentation/) | ||
[![Travis CI](https://img.shields.io/travis/conqa/serverless-openapi-documentation.svg)](https://travis-ci.org/conqa/serverless-openapi-documentation) | ||
Generates [**OpenAPI 3.0.0**](https://github.com/OAI/OpenAPI-Specification/blob/3.0.0/versions/3.0.0.md) documentation from serverless configuration files. OpenAPI is formerly known as Swagger. The configuration is inspired by the format used in [serverless-aws-documentation](https://www.npmjs.com/package/serverless-aws-documentation). | ||
Works well with [Lincoln OpenAPI Renderer](https://github.com/temando/open-api-renderer). | ||
Works well with [ReDoc](https://github.com/Rebilly/ReDoc). | ||
@@ -23,3 +22,3 @@ --- | ||
- [`methodResponses`](#methodresponses) | ||
- [Example Configuration](#example-configuration) | ||
- [Example configuration](#example-configuration) | ||
- [Install](#install) | ||
@@ -64,2 +63,6 @@ | ||
description: 'This is my API' | ||
# https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#securitySchemeObject | ||
securitySchemes: {} | ||
# https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#security-requirement-object | ||
security: {} | ||
models: {} | ||
@@ -94,3 +97,6 @@ ``` | ||
* `contentType`: the content type of the described request/response (ie. `application/json` or `application/xml`). | ||
* `schema`: The JSON Schema ([website](http://json-schema.org/)) that describes the model. You can either use inline `YAML` to define these, or refer to an external schema file as below | ||
* `schema`: The JSON Schema ([website](http://json-schema.org/)) that describes the model. You can either: | ||
- use inline `YAML` to define these | ||
- use serverless' functionality to merge in external schema file | ||
- specify a path to json schema in which case if you reuse some types in multiple schemas - they will be included in resulting components once instead of duplicated for each referencing schema | ||
@@ -101,12 +107,4 @@ ```yml | ||
models: | ||
- name: "ErrorResponse" | ||
description: "This is an error" | ||
contentType: "application/json" | ||
schema: ${file(models/ErrorResponse.json)} | ||
- name: "PutDocumentResponse" | ||
description: "PUT Document response model (external reference example)" | ||
contentType: "application/json" | ||
schema: ${file(models/PutDocumentResponse.json)} | ||
- name: "PutDocumentRequest" | ||
description: "PUT Document request model (inline example)" | ||
description: "Inline schema example" | ||
contentType: "application/json" | ||
@@ -121,2 +119,10 @@ schema: | ||
type: "string" | ||
- name: "PutDocumentResponse" | ||
description: "External file merge example" | ||
contentType: "application/json" | ||
schema: ${file(models/PutDocumentResponse.json)} | ||
- name: "ErrorResponse" | ||
description: "Path to a schema example" | ||
contentType: "application/json" | ||
schema: models/ErrorResponse.json | ||
``` | ||
@@ -320,3 +326,3 @@ | ||
```bash | ||
npm install serverless-openapi-documentation --save-dev | ||
npm install @conqa/serverless-openapi-documentation --save-dev | ||
``` | ||
@@ -326,3 +332,3 @@ | ||
```bash | ||
yarn add serverless-openapi-documentation --dev | ||
yarn add @conqa/serverless-openapi-documentation --dev | ||
``` | ||
@@ -334,3 +340,3 @@ | ||
plugins: | ||
- serverless-openapi-documentation | ||
- @conqa/serverless-openapi-documentation | ||
``` | ||
@@ -337,0 +343,0 @@ |
@@ -1,2 +0,24 @@ | ||
import { ILog } from './types'; | ||
import * as Serverless from "serverless"; | ||
import { Format, DefinitionConfig } from "./types"; | ||
interface Options { | ||
indent: number; | ||
format: Format; | ||
output: string; | ||
} | ||
interface ProcessedInput { | ||
options: Options; | ||
} | ||
interface CustomVars { | ||
documentation: DefinitionConfig; | ||
} | ||
interface Service { | ||
custom: CustomVars; | ||
} | ||
interface Variables { | ||
service: Service; | ||
} | ||
interface FullServerless extends Serverless { | ||
variables: Variables; | ||
processedInput: ProcessedInput; | ||
} | ||
export declare class ServerlessOpenApiDocumentation { | ||
@@ -7,4 +29,2 @@ hooks: any; | ||
private serverless; | ||
/** CLI options */ | ||
private options; | ||
/** Serverless Service Custom vars */ | ||
@@ -17,13 +37,14 @@ private customVars; | ||
*/ | ||
constructor(serverless: any, options: any); | ||
log: ILog; | ||
constructor(serverless: FullServerless, options: any); | ||
private log; | ||
/** | ||
* Generates OpenAPI Documentation based on serverless configuration and functions | ||
*/ | ||
generate(): Promise<void>; | ||
/** | ||
* Processes CLI input by reading the input from serverless | ||
* @returns config IConfigType | ||
*/ | ||
private processCliInput(); | ||
/** | ||
* Generates OpenAPI Documentation based on serverless configuration and functions | ||
*/ | ||
private generate(); | ||
private processCliInput; | ||
} | ||
export {}; |
"use strict"; | ||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
return new (P || (P = Promise))(function (resolve, reject) { | ||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } | ||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } | ||
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } | ||
step((generator = generator.apply(thisArg, _arguments || [])).next()); | ||
}); | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const c = require("chalk"); | ||
const chalk_1 = require("chalk"); | ||
const fs = require("fs"); | ||
const YAML = require("js-yaml"); | ||
const _ = require("lodash"); | ||
const util_1 = require("util"); | ||
const DefinitionGenerator_1 = require("./DefinitionGenerator"); | ||
const utils_1 = require("./utils"); | ||
const types_1 = require("./types"); | ||
class ServerlessOpenApiDocumentation { | ||
@@ -17,8 +26,6 @@ /** | ||
this.log = (...str) => { | ||
process.stdout.write(str.join(' ')); | ||
process.stdout.write(str.join(" ")); | ||
}; | ||
// pull the serverless instance into our class vars | ||
this.serverless = serverless; | ||
// pull the CLI options into our class vars | ||
this.options = options; | ||
// Serverless service custom variables | ||
@@ -31,30 +38,85 @@ this.customVars = this.serverless.variables.service.custom; | ||
generate: { | ||
lifecycleEvents: [ | ||
'serverless', | ||
], | ||
usage: 'Generate OpenAPI v3 Documentation', | ||
lifecycleEvents: ["serverless"], | ||
usage: "Generate OpenAPI v3 Documentation", | ||
options: { | ||
output: { | ||
usage: 'Output file location [default: openapi.yml|json]', | ||
shortcut: 'o', | ||
usage: "Output file location [default: openapi.yml|json]", | ||
shortcut: "o" | ||
}, | ||
format: { | ||
usage: 'OpenAPI file format (yml|json) [default: yml]', | ||
shortcut: 'f', | ||
usage: "OpenAPI file format (yml|json) [default: yml]", | ||
shortcut: "f" | ||
}, | ||
indent: { | ||
usage: 'File indentation in spaces [default: 2]', | ||
shortcut: 'i', | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
usage: "File indentation in spaces [default: 2]", | ||
shortcut: "i" | ||
} | ||
} | ||
} | ||
} | ||
} | ||
}; | ||
// Declare the hooks our plugin is interested in | ||
this.hooks = { | ||
'openapi:generate:serverless': this.generate.bind(this), | ||
"openapi:generate:serverless": this.generate.bind(this) | ||
}; | ||
} | ||
/** | ||
* Generates OpenAPI Documentation based on serverless configuration and functions | ||
*/ | ||
generate() { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
this.log(chalk_1.default.bold.underline("OpenAPI v3 Documentation Generator\n\n")); | ||
// Instantiate DocumentGenerator | ||
const generator = new DefinitionGenerator_1.DefinitionGenerator(this.customVars.documentation, this.serverless.config.servicePath); | ||
yield generator.parse(); | ||
// Map function configurations | ||
const funcConfigs = this.serverless.service | ||
.getAllFunctions() | ||
.map(functionName => { | ||
const func = this.serverless.service.getFunction(functionName); | ||
return _.merge({ _functionName: functionName }, func); | ||
}); | ||
// Add Paths to OpenAPI Output from Function Configuration | ||
generator.readFunctions(funcConfigs); | ||
// Process CLI Input options | ||
const config = this.processCliInput(); | ||
this.log(`${chalk_1.default.bold.yellow("[VALIDATION]")} Validating OpenAPI generated output\n`); | ||
const validation = generator.validate(); | ||
if (validation.valid) { | ||
this.log(`${chalk_1.default.bold.green("[VALIDATION]")} OpenAPI valid: ${chalk_1.default.bold.green("true")}\n\n`); | ||
} | ||
else { | ||
this.log(`${chalk_1.default.bold.red("[VALIDATION]")} Failed to validate OpenAPI document: \n\n`); | ||
this.log(`${chalk_1.default.bold.green("Context:")} ${JSON.stringify(validation.context, null, 2)}\n`); | ||
if (typeof validation.error === "string") { | ||
this.log(`${validation.error}\n\n`); | ||
} | ||
else { | ||
for (const info of validation.error) { | ||
this.log(chalk_1.default.grey("\n\n--------\n\n")); | ||
this.log(" ", chalk_1.default.blue(info.dataPath), "\n"); | ||
this.log(" ", info.schemaPath, chalk_1.default.bold.yellow(info.message)); | ||
this.log(chalk_1.default.grey("\n\n--------\n\n")); | ||
this.log(`${util_1.inspect(info, { colors: true, depth: 2 })}\n\n`); | ||
} | ||
} | ||
} | ||
const { definition } = generator; | ||
// Output the OpenAPI document to the correct format | ||
let output; | ||
switch (config.format.toLowerCase()) { | ||
case "json": | ||
output = JSON.stringify(definition, null, config.indent); | ||
break; | ||
case "yaml": | ||
default: | ||
output = YAML.safeDump(definition, { indent: config.indent }); | ||
break; | ||
} | ||
fs.writeFileSync(config.file, output); | ||
this.log(`${chalk_1.default.bold.green("[OUTPUT]")} To "${chalk_1.default.bold.red(config.file)}"\n`); | ||
}); | ||
} | ||
/** | ||
* Processes CLI input by reading the input from serverless | ||
@@ -65,71 +127,20 @@ * @returns config IConfigType | ||
const config = { | ||
format: 'yaml', | ||
file: 'openapi.yml', | ||
indent: 2, | ||
format: types_1.Format.yaml, | ||
file: "openapi.yml", | ||
indent: 2 | ||
}; | ||
config.indent = this.serverless.processedInput.options.indent || 2; | ||
config.format = this.serverless.processedInput.options.format || 'yaml'; | ||
if (['yaml', 'json'].indexOf(config.format.toLowerCase()) < 0) { | ||
config.format = | ||
this.serverless.processedInput.options.format || types_1.Format.yaml; | ||
if ([types_1.Format.yaml, types_1.Format.json].indexOf(config.format) < 0) { | ||
throw new Error('Invalid Output Format Specified - must be one of "yaml" or "json"'); | ||
} | ||
config.file = this.serverless.processedInput.options.output || | ||
((config.format === 'yaml') ? 'openapi.yml' : 'openapi.json'); | ||
this.log(`${c.bold.green('[OPTIONS]')}`, `format: "${c.bold.red(config.format)}",`, `output file: "${c.bold.red(config.file)}",`, `indentation: "${c.bold.red(String(config.indent))}"\n\n`); | ||
config.file = | ||
this.serverless.processedInput.options.output || | ||
(config.format === "yaml" ? "openapi.yml" : "openapi.json"); | ||
this.log(`${chalk_1.default.bold.green("[OPTIONS]")}`, `format: "${chalk_1.default.bold.red(config.format)}",`, `output file: "${chalk_1.default.bold.red(config.file)}",`, `indentation: "${chalk_1.default.bold.red(String(config.indent))}"\n\n`); | ||
return config; | ||
} | ||
/** | ||
* Generates OpenAPI Documentation based on serverless configuration and functions | ||
*/ | ||
generate() { | ||
this.log(c.bold.underline('OpenAPI v3 Documentation Generator\n\n')); | ||
// Instantiate DocumentGenerator | ||
const generator = new DefinitionGenerator_1.DefinitionGenerator(this.customVars.documentation); | ||
generator.parse(); | ||
// Map function configurations | ||
const funcConfigs = this.serverless.service.getAllFunctions().map((functionName) => { | ||
const func = this.serverless.service.getFunction(functionName); | ||
return utils_1.merge({ _functionName: functionName }, func); | ||
}); | ||
// Add Paths to OpenAPI Output from Function Configuration | ||
generator.readFunctions(funcConfigs); | ||
// Process CLI Input options | ||
const config = this.processCliInput(); | ||
this.log(`${c.bold.yellow('[VALIDATION]')} Validating OpenAPI generated output\n`); | ||
const validation = generator.validate(); | ||
if (validation.valid) { | ||
this.log(`${c.bold.green('[VALIDATION]')} OpenAPI valid: ${c.bold.green('true')}\n\n`); | ||
} | ||
else { | ||
this.log(`${c.bold.red('[VALIDATION]')} Failed to validate OpenAPI document: \n\n`); | ||
this.log(`${c.bold.green('Context:')} ${JSON.stringify(validation.context, null, 2)}\n`); | ||
if (typeof validation.error === 'string') { | ||
this.log(`${validation.error}\n\n`); | ||
} | ||
else { | ||
for (const info of validation.error) { | ||
this.log(c.grey('\n\n--------\n\n')); | ||
this.log(' ', c.blue(info.dataPath), '\n'); | ||
this.log(' ', info.schemaPath, c.bold.yellow(info.message)); | ||
this.log(c.grey('\n\n--------\n\n')); | ||
this.log(`${util_1.inspect(info, { colors: true, depth: 2 })}\n\n`); | ||
} | ||
} | ||
} | ||
const { definition } = generator; | ||
// Output the OpenAPI document to the correct format | ||
let output; | ||
switch (config.format.toLowerCase()) { | ||
case 'json': | ||
output = JSON.stringify(definition, null, config.indent); | ||
break; | ||
case 'yaml': | ||
default: | ||
output = YAML.safeDump(definition, { indent: config.indent }); | ||
break; | ||
} | ||
fs.writeFileSync(config.file, output); | ||
this.log(`${c.bold.green('[OUTPUT]')} To "${c.bold.red(config.file)}"\n`); | ||
} | ||
} | ||
exports.ServerlessOpenApiDocumentation = ServerlessOpenApiDocumentation; | ||
//# sourceMappingURL=ServerlessOpenApiDocumentation.js.map |
@@ -1,22 +0,30 @@ | ||
import { JSONSchema7 } from 'json-schema'; | ||
export interface IModels { | ||
import { JSONSchema7 } from "json-schema"; | ||
import { OpenAPIV3 } from "openapi-types"; | ||
export interface Model { | ||
name: string; | ||
description: string; | ||
contentType: string; | ||
schema: JSONSchema7; | ||
examples: any[]; | ||
schema: string | JSONSchema7; | ||
examples: Array<any>; | ||
example: object; | ||
} | ||
export interface IDefinitionConfig { | ||
export interface DefinitionConfig { | ||
title: string; | ||
description: string; | ||
version?: string; | ||
models: IModels[]; | ||
securitySchemes: OpenAPIV3.SecuritySchemeObject; | ||
security: Array<OpenAPIV3.SecurityRequirementObject>; | ||
servers: Array<OpenAPIV3.ServerObject>; | ||
models: Array<Model>; | ||
} | ||
export interface IDefinitionType { | ||
export declare enum Format { | ||
yaml = "yaml", | ||
json = "json" | ||
} | ||
export interface DefinitionType { | ||
file: string; | ||
format: 'yaml' | 'json'; | ||
format: Format; | ||
indent: number; | ||
} | ||
export interface IServerlessFunctionConfig { | ||
export interface ServerlessFunctionConfig { | ||
_functionName: string; | ||
@@ -26,6 +34,6 @@ handler: string; | ||
environment?: object; | ||
events?: any[]; | ||
events?: Array<any>; | ||
} | ||
export interface IOperation { | ||
tags?: string[]; | ||
export interface Operation { | ||
tags?: Array<string>; | ||
summary?: string; | ||
@@ -35,3 +43,3 @@ description?: string; | ||
operationId?: string; | ||
parameters?: IParameterConfig[]; | ||
parameters?: Array<ParameterConfig>; | ||
requestBody?: any; | ||
@@ -41,8 +49,8 @@ responses?: any; | ||
deprecated?: boolean; | ||
security?: any[]; | ||
servers?: any[]; | ||
security?: Array<any>; | ||
servers?: Array<any>; | ||
} | ||
export interface IParameterConfig { | ||
export interface ParameterConfig { | ||
name: string; | ||
in: 'path' | 'query' | 'header' | 'cookie'; | ||
in: "path" | "query" | "header" | "cookie"; | ||
description: string; | ||
@@ -53,19 +61,19 @@ required?: boolean; | ||
allowEmptyValue?: boolean; | ||
style?: 'form' | 'simple'; | ||
style?: "form" | "simple"; | ||
explode?: boolean; | ||
allowReserved?: boolean; | ||
example?: any; | ||
examples?: any[]; | ||
examples?: Array<any>; | ||
content?: Map<string, any>; | ||
} | ||
export interface IDefinition { | ||
export interface Definition { | ||
openapi: string; | ||
info: any; | ||
servers?: any[]; | ||
paths: any; | ||
info?: any; | ||
servers?: Array<any>; | ||
paths?: any; | ||
components?: any; | ||
security?: any[]; | ||
tags?: any[]; | ||
externalDocs: any; | ||
security?: Array<any>; | ||
tags?: Array<any>; | ||
externalDocs?: any; | ||
} | ||
export declare type ILog = (...str: string[]) => void; | ||
export declare type ILog = (...str: Array<string>) => void; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
var Format; | ||
(function (Format) { | ||
Format["yaml"] = "yaml"; | ||
Format["json"] = "json"; | ||
})(Format = exports.Format || (exports.Format = {})); | ||
//# sourceMappingURL=types.js.map |
@@ -1,5 +0,1 @@ | ||
import { IMerge } from 'lutils'; | ||
export declare const merge: IMerge; | ||
export declare const clone: <S>(source: S) => S; | ||
export declare function isIterable(obj: any): boolean; | ||
export declare function omit<T extends object>(obj: T, keys: string[]): T; | ||
export declare const cleanSchema: (schema: any) => any; |
20
utils.js
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const lutils_1 = require("lutils"); | ||
exports.merge = new lutils_1.Merge({ depth: 100 }).merge; | ||
exports.clone = new lutils_1.Clone({ depth: 100 }).clone; | ||
function isIterable(obj) { | ||
if (obj === null || obj === undefined) { | ||
return false; | ||
} | ||
return typeof obj[Symbol.iterator] === 'function'; | ||
} | ||
exports.isIterable = isIterable; | ||
function omit(obj, keys) { | ||
const cloned = exports.clone(obj); | ||
for (const key of keys) { | ||
delete cloned[key]; | ||
} | ||
return cloned; | ||
} | ||
exports.omit = omit; | ||
const _ = require("lodash"); | ||
exports.cleanSchema = schema => _.omit(schema, "$schema", "definitions"); | ||
//# sourceMappingURL=utils.js.map |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
360979
23
9140
349
8
24
2
+ Addedlodash@^4.17.11
+ Addedformat-util@1.0.5(transitive)
+ Addedjson-schema-ref-parser@6.1.0(transitive)
+ Addedlodash@4.17.21(transitive)
+ Addedono@4.0.11(transitive)
- Removedlutils@^2.4.0
- Removedlutils@2.4.0(transitive)