From e43573ec0bc8ca2b0db2a48d8c7d7c09b556d96b Mon Sep 17 00:00:00 2001 From: michaill Date: Sat, 26 Mar 2022 00:27:54 +0100 Subject: [PATCH 1/6] use glob for reading api paths --- lib/helpers/getSpecificationObject.ts | 8 +++++--- lib/helpers/parseApiFile.ts | 11 +++++++---- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/lib/helpers/getSpecificationObject.ts b/lib/helpers/getSpecificationObject.ts index 3181453..8755b1a 100644 --- a/lib/helpers/getSpecificationObject.ts +++ b/lib/helpers/getSpecificationObject.ts @@ -4,7 +4,7 @@ import { finalizeSpecificationObject } from './finalizeSpecificationObject.ts'; import { updateSpecificationObject } from './updateSpecificationObject.ts'; -export function getSpecificationObject(options : any) { +export function getSpecificationObject(options: any) { // Get input definition and prepare the specification's skeleton const definition = options.swaggerDefinition || options.definition; const specification = createSpecification(definition); @@ -13,8 +13,10 @@ export function getSpecificationObject(options : any) { const apiPaths = options.apis; for (let i = 0; i < apiPaths.length; i += 1) { - const parsedFile = parseApiFile(apiPaths[i]); - updateSpecificationObject(parsedFile, specification); + const parsedFiles = parseApiFile(apiPaths[i]); + parsedFiles.forEach(parsedFile => { + updateSpecificationObject(parsedFile, specification); + }); } return finalizeSpecificationObject(specification); diff --git a/lib/helpers/parseApiFile.ts b/lib/helpers/parseApiFile.ts index b53768a..63fca04 100644 --- a/lib/helpers/parseApiFile.ts +++ b/lib/helpers/parseApiFile.ts @@ -1,4 +1,5 @@ import * as path from "https://deno.land/std/path/mod.ts"; +import { expandGlobSync } from "https://deno.land/std/fs/mod.ts"; import { parseApiFileContent } from "./parseApiFileContent.ts"; /** * Parses the provided API file for JSDoc comments. @@ -7,8 +8,10 @@ import { parseApiFileContent } from "./parseApiFileContent.ts"; * @returns {{jsdoc: array, yaml: array}} JSDoc comments and Yaml files */ export function parseApiFile(file: any) { - const fileContent = Deno.readTextFileSync(file); - const ext = path.extname(file); - - return parseApiFileContent(fileContent, ext); + return Array.from(expandGlobSync(file)).map( + f => parseApiFileContent( + Deno.readTextFileSync(f.path), + path.extname(f.path) + ) + ); } From 19f687e3a0eafd30bd5d30d2bdf0692b2448b97e Mon Sep 17 00:00:00 2001 From: michaill Date: Sun, 27 Mar 2022 22:42:29 +0200 Subject: [PATCH 2/6] readme + example + test update --- README.md | 3 ++- example/v2/app.ts | 3 ++- example/v2/nested/nested/nested/nesteParams3.yaml | 7 +++++++ example/v2/nested/nested/nestedParams2.yaml | 7 +++++++ example/v2/nested/nestedParams.yaml | 7 +++++++ test/test.ts | 2 +- test/testData.ts | 2 +- 7 files changed, 27 insertions(+), 4 deletions(-) create mode 100644 example/v2/nested/nested/nested/nesteParams3.yaml create mode 100644 example/v2/nested/nested/nestedParams2.yaml create mode 100644 example/v2/nested/nestedParams.yaml diff --git a/README.md b/README.md index 4253ba4..a105179 100644 --- a/README.md +++ b/README.md @@ -28,13 +28,14 @@ const swaggerDefinition = { }, host: `localhost:8000`, // Host (optional) basePath: '/', // Base path (optional) + swagger: '2.0', // Swagger version (optional) }; const options = { swaggerDefinition, // Path to the API docs // Note that this path is relative to the current directory from which the Node.js is ran, not the application itself. - apis: ['./example/v2/routes.ts', './example/v2/parameters.yaml'], + apis: ['./example/v2/routes.ts', './example/v2/**/*.yaml'], }; // Initialize swagger-jsdoc -> returns validated swagger spec in json format diff --git a/example/v2/app.ts b/example/v2/app.ts index 6f1e926..4301164 100644 --- a/example/v2/app.ts +++ b/example/v2/app.ts @@ -11,6 +11,7 @@ const swaggerDefinition = { }, host: `localhost:8000`, // Host (optional) basePath: '/', // Base path (optional) + swagger: '2.0', // Swagger version (optional) }; // Options for the swagger docs @@ -19,7 +20,7 @@ const options = { swaggerDefinition, // Path to the API docs // Note that this path is relative to the current directory from which the Node.js is ran, not the application itself. - apis: ['./example/v2/routes.ts', './example/v2/parameters.yaml'], + apis: ['./example/v2/routes.ts', './example/v2/**/*.yaml'], }; // Initialize swagger-jsdoc -> returns validated swagger spec in json format diff --git a/example/v2/nested/nested/nested/nesteParams3.yaml b/example/v2/nested/nested/nested/nesteParams3.yaml new file mode 100644 index 0000000..c4619f2 --- /dev/null +++ b/example/v2/nested/nested/nested/nesteParams3.yaml @@ -0,0 +1,7 @@ +parameters: + nestedLvl3: + name: nestedLvl3 + description: Parameters nested in folder 3 times + in: formData + required: true + type: string diff --git a/example/v2/nested/nested/nestedParams2.yaml b/example/v2/nested/nested/nestedParams2.yaml new file mode 100644 index 0000000..d4fbed8 --- /dev/null +++ b/example/v2/nested/nested/nestedParams2.yaml @@ -0,0 +1,7 @@ +parameters: + nestedLvl2: + name: nestedLvl2 + description: Parameters nested in folder 2 times + in: formData + required: true + type: string diff --git a/example/v2/nested/nestedParams.yaml b/example/v2/nested/nestedParams.yaml new file mode 100644 index 0000000..8942a77 --- /dev/null +++ b/example/v2/nested/nestedParams.yaml @@ -0,0 +1,7 @@ +parameters: + nestedLvl1: + name: nestedLvl1 + description: Parameters nested in folder 1 time + in: formData + required: true + type: string diff --git a/test/test.ts b/test/test.ts index 4e929b5..8543228 100644 --- a/test/test.ts +++ b/test/test.ts @@ -14,7 +14,7 @@ const swaggerDefinition = { const options = { swaggerDefinition, - apis: ['./example/v2/routes.ts', './example/v2/parameters.yaml'], + apis: ['./example/v2/routes.ts', './example/v2/**/*.yaml'], }; Deno.test('swaggerDoc()', async () => { diff --git a/test/testData.ts b/test/testData.ts index 67bf5b2..d36c424 100644 --- a/test/testData.ts +++ b/test/testData.ts @@ -1 +1 @@ -export const SwaggerJson: string = `{"info":{"title":"Hello World","version":"1.0.0","description":"A sample API"},"host":"localhost:8000","basePath":"/","swagger":"2.0","paths":{"/":{"get":{"description":"Returns the homepage","responses":{"200":{"description":"hello world"}}}}},"definitions":{},"responses":{},"parameters":{"username":{"name":"username","description":"Username to use for login.","in":"formData","required":true,"type":"string"}},"securityDefinitions":{},"tags":[]}`; \ No newline at end of file +export const SwaggerJson: string = `{"info":{"title":"Hello World","version":"1.0.0","description":"A sample API"},"host":"localhost:8000","basePath":"/","swagger":"2.0","paths":{"/":{"get":{"description":"Returns the homepage","responses":{"200":{"description":"hello world"}}}}},"definitions":{},"responses":{},"parameters":{"nestedLvl3":{"name":"nestedLvl3","description":"Parameters nested in folder 3 times","in":"formData","required":true,"type":"string"},"nestedLvl2":{"name":"nestedLvl2","description":"Parameters nested in folder 2 times","in":"formData","required":true,"type":"string"},"nestedLvl1":{"name":"nestedLvl1","description":"Parameters nested in folder 1 time","in":"formData","required":true,"type":"string"},"username":{"name":"username","description":"Username to use for login.","in":"formData","required":true,"type":"string"}},"securityDefinitions":{},"tags":[]}`; \ No newline at end of file From b026c71ed14952ec43ec42804c2b482d21b49d7b Mon Sep 17 00:00:00 2001 From: michaill Date: Fri, 1 Apr 2022 22:13:54 +0200 Subject: [PATCH 3/6] enum generator + openapi3 fixed --- example/v2/app.ts | 8 +++-- example/v2/parser.ts | 25 +++++++++++++++ lib/helpers/createSpecification.ts | 2 -- lib/helpers/getSpecificationObject.ts | 8 ++--- lib/helpers/parseApiFile.ts | 6 ++-- lib/helpers/parseApiFileContent.ts | 39 ++++++++++++++++++++++-- lib/helpers/specification.ts | 1 + lib/helpers/updateSpecificationObject.ts | 8 +++-- lib/index.ts | 4 +-- 9 files changed, 83 insertions(+), 18 deletions(-) create mode 100644 example/v2/parser.ts diff --git a/example/v2/app.ts b/example/v2/app.ts index 4301164..519f9f9 100644 --- a/example/v2/app.ts +++ b/example/v2/app.ts @@ -11,7 +11,7 @@ const swaggerDefinition = { }, host: `localhost:8000`, // Host (optional) basePath: '/', // Base path (optional) - swagger: '2.0', // Swagger version (optional) + openapi: '3.0.0', // Swagger version (optional) }; // Options for the swagger docs @@ -20,7 +20,11 @@ const options = { swaggerDefinition, // Path to the API docs // Note that this path is relative to the current directory from which the Node.js is ran, not the application itself. - apis: ['./example/v2/routes.ts', './example/v2/**/*.yaml'], + apis: [ + './example/v2/routes.ts', + './example/v2/**/*.yaml', + './example/v2/parser.ts' + ], }; // Initialize swagger-jsdoc -> returns validated swagger spec in json format diff --git a/example/v2/parser.ts b/example/v2/parser.ts new file mode 100644 index 0000000..65216de --- /dev/null +++ b/example/v2/parser.ts @@ -0,0 +1,25 @@ +/** + * @swagger-enum + */ +export enum TestingEnum { + VALUE1, + VALUE2, + VALUE3 +} + +/** + * @swagger-enum + */ +export enum TestingEnumWithCustomValues { + VALUE1 = 'val1', + VALUE2 = 'val2', + VALUE3 = 'val3' +} + +export type TestType = string | number; + +export interface TestInterface { + name: string; + type: TestType; + enum: TestingEnum; +} \ No newline at end of file diff --git a/lib/helpers/createSpecification.ts b/lib/helpers/createSpecification.ts index cd2c14e..27e100d 100644 --- a/lib/helpers/createSpecification.ts +++ b/lib/helpers/createSpecification.ts @@ -21,12 +21,10 @@ export function createSpecification(definition: any) { const v3 = [...v2, 'components']; if (specification.openapi) { - specification.openapi = specification.openapi; v3.forEach((property) => { specification[property] = specification[property] || {}; }); } else if (specification.swagger) { - specification.swagger = specification.swagger; v2.forEach((property) => { specification[property] = specification[property] || {}; }); diff --git a/lib/helpers/getSpecificationObject.ts b/lib/helpers/getSpecificationObject.ts index 8755b1a..1a7a85f 100644 --- a/lib/helpers/getSpecificationObject.ts +++ b/lib/helpers/getSpecificationObject.ts @@ -4,7 +4,7 @@ import { finalizeSpecificationObject } from './finalizeSpecificationObject.ts'; import { updateSpecificationObject } from './updateSpecificationObject.ts'; -export function getSpecificationObject(options: any) { +export async function getSpecificationObject(options: any) { // Get input definition and prepare the specification's skeleton const definition = options.swaggerDefinition || options.definition; const specification = createSpecification(definition); @@ -13,9 +13,9 @@ export function getSpecificationObject(options: any) { const apiPaths = options.apis; for (let i = 0; i < apiPaths.length; i += 1) { - const parsedFiles = parseApiFile(apiPaths[i]); - parsedFiles.forEach(parsedFile => { - updateSpecificationObject(parsedFile, specification); + const parsedFiles = await parseApiFile(apiPaths[i]); + await parsedFiles.forEach(async parsedFile => { + await updateSpecificationObject(parsedFile, specification); }); } diff --git a/lib/helpers/parseApiFile.ts b/lib/helpers/parseApiFile.ts index 63fca04..a25bf39 100644 --- a/lib/helpers/parseApiFile.ts +++ b/lib/helpers/parseApiFile.ts @@ -7,9 +7,9 @@ import { parseApiFileContent } from "./parseApiFileContent.ts"; * @param {string} file - File to be parsed * @returns {{jsdoc: array, yaml: array}} JSDoc comments and Yaml files */ -export function parseApiFile(file: any) { - return Array.from(expandGlobSync(file)).map( - f => parseApiFileContent( +export async function parseApiFile(file: any) { + return await Array.from(expandGlobSync(file)).map( + async f => await parseApiFileContent( Deno.readTextFileSync(f.path), path.extname(f.path) ) diff --git a/lib/helpers/parseApiFileContent.ts b/lib/helpers/parseApiFileContent.ts index a118e18..d4997cc 100644 --- a/lib/helpers/parseApiFileContent.ts +++ b/lib/helpers/parseApiFileContent.ts @@ -1,6 +1,7 @@ import jsYaml from 'https://dev.jspm.io/js-yaml'; import doctrine from 'https://dev.jspm.io/doctrine'; + /** * Parse the provided API file content. * @@ -10,7 +11,7 @@ import doctrine from 'https://dev.jspm.io/doctrine'; * @returns {{jsdoc: array, yaml: array}} JSDoc comments and Yaml files * @requires doctrine */ -export function parseApiFileContent(fileContent: any, ext: any) { +export async function parseApiFileContent(fileContent: any, ext: any) { const jsDocRegex = /\/\*\*([\s\S]*?)\*\//gm; const yaml = []; const jsDocComments = []; @@ -21,12 +22,46 @@ export function parseApiFileContent(fileContent: any, ext: any) { const regexResults = fileContent.match(jsDocRegex); if (regexResults) { for (let i = 0; i < regexResults.length; i += 1) { - const jsDocComment = doctrine.parse(regexResults[i], { unwrap: true }); + if (regexResults[i].indexOf('@swagger-enum') !== -1) { + console.log("myResult", regexResults[i]); + const endOfJsDocComment = fileContent.indexOf(regexResults[i]) + regexResults[i].length; + fileContent = fileContent.substring(endOfJsDocComment); + const [all, enumName, enumDef] = fileContent.match(/enum\s([^\s]*)\s{([^}]*)}/m); + console.log("enumFound", enumName); + if (enumName && enumDef) { + const enumItems = enumDef.split(/[\r\n]+/).filter((i: string) => i.trim() !== ''); + if (enumItems.length) { + let itemsString = ''; + let type = 'number'; + enumItems.forEach((item: string) => { + if (item.indexOf('=') === -1) { + itemsString += '* - ' + item.trim().replace(/,$/, '') + '\n'; + } else { + type = 'string'; + const itemParts = item.split('='); + itemsString += ' * - ' + itemParts[1].trim().replace(/,$/, '') + '\n'; + } + }); + regexResults[i] = `/** + * @swagger + * components: + * schemas: + * ${enumName}: + * type: ${type} + * enum: +${itemsString} */`; + console.log("final swagger", regexResults[i]); + } + } + } + const jsDocComment = await doctrine.parse(regexResults[i], { unwrap: true }); jsDocComments.push(jsDocComment); } } } + console.log("parseContent", yaml, jsDocComments); + return { yaml, jsdoc: jsDocComments, diff --git a/lib/helpers/specification.ts b/lib/helpers/specification.ts index f0a907b..42ac0c3 100644 --- a/lib/helpers/specification.ts +++ b/lib/helpers/specification.ts @@ -151,6 +151,7 @@ function organizeSwaggerProperties(swaggerObject : any, pathObject : any, proper * comments */ export function addDataToSwaggerObject(swaggerObject : any, data : any) { + console.log("addData", swaggerObject, data); if (!swaggerObject || !data) { throw new Error('swaggerObject and data are required!'); } diff --git a/lib/helpers/updateSpecificationObject.ts b/lib/helpers/updateSpecificationObject.ts index 2d705c2..0072ab9 100644 --- a/lib/helpers/updateSpecificationObject.ts +++ b/lib/helpers/updateSpecificationObject.ts @@ -8,11 +8,13 @@ import { filterJsDocComments } from './filterJsDocComments.ts'; * @param {object} parsedFile - Parsed API file. * @param {object} specification - Specification accumulator. */ -export function updateSpecificationObject(parsedFile :any, specification: any) { - addDataToSwaggerObject(specification, parsedFile.yaml); +export async function updateSpecificationObject(parsedFile :any, specification: any) { + console.log("parsedFile", parsedFile); + const {yaml, jsdoc} = await parsedFile; + addDataToSwaggerObject(specification, yaml); addDataToSwaggerObject( specification, - filterJsDocComments(parsedFile.jsdoc) + filterJsDocComments(jsdoc) ); } \ No newline at end of file diff --git a/lib/index.ts b/lib/index.ts index 92c8d31..4e1e277 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -14,13 +14,13 @@ import { getSpecificationObject } from './helpers/getSpecificationObject.ts'; * @returns {object} Output specification * @requires swagger-parser */ -export function swaggerDoc (options : any) { +export async function swaggerDoc (options : any) { if ((!options.swaggerDefinition || !options.definition) && !options.apis) { throw new Error('Provided options are incorrect.'); } try { - const specificationObject = getSpecificationObject(options); + const specificationObject = await getSpecificationObject(options); return specificationObject; } catch (err) { From 71f84a481206247f0ed6174acf03a5a7d08c4951 Mon Sep 17 00:00:00 2001 From: michaill Date: Fri, 1 Apr 2022 23:25:44 +0200 Subject: [PATCH 4/6] examples + test + readme update --- README.md | 12 +++++++ example/v2/app.ts | 7 ++-- example/v2/parser.ts | 25 ------------- example/v3/app.ts | 45 ++++++++++++++++++++++++ example/v3/enum.ts | 35 ++++++++++++++++++ example/v3/routes.ts | 44 +++++++++++++++++++++++ lib/helpers/parseApiFileContent.ts | 13 +++---- lib/helpers/specification.ts | 1 - lib/helpers/updateSpecificationObject.ts | 1 - test.sh | 2 +- test/test.ts | 38 +++++++++++++++----- test/testData.ts | 2 +- test/testDataV3.ts | 1 + 13 files changed, 176 insertions(+), 50 deletions(-) delete mode 100644 example/v2/parser.ts create mode 100644 example/v3/app.ts create mode 100644 example/v3/enum.ts create mode 100644 example/v3/routes.ts create mode 100644 test/testDataV3.ts diff --git a/README.md b/README.md index a105179..91ef012 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,8 @@ To make sure your end specification is valid, do read the most up-to date offici **swagger-jsdoc** enables you to integrate [Swagger](http://swagger.io) using [`JSDoc`](https://jsdoc.app/) comments in your code. Just add `@swagger` on top of your DocBlock and declare the meaning of your code in YAML complying to the OpenAPI specification. +**Enums can be generated automatically (openapi 3.x ONLY)** - just add `@swagger-enum` instead of `@swagger` mentioned above and everything will be autogenerated from the following definition ([see example](./example/v3/enum.ts)). Then you can refer to this enum when you use `$ref: '#/components/schemas/EnumName'` instead of type definition in some schema ([see example](./example/v3/routes.ts)) + ```ts import { swaggerDoc } from "https://deno.land/x/deno_swagger_doc/mod.ts"; @@ -29,6 +31,7 @@ const swaggerDefinition = { host: `localhost:8000`, // Host (optional) basePath: '/', // Base path (optional) swagger: '2.0', // Swagger version (optional) + openapi: '3.0.0', // Openapi version (optional) }; const options = { @@ -61,6 +64,15 @@ If you facing any issue due to TypeScript type checking. Please use the `--no-ch `denon run --no-check --allow-net --allow-read --unstable ./example/v2/app.ts` +**openapi 3.x version** +`deno run --no-check --allow-net --allow-read --unstable ./example/v3/app.ts` + +## Breaking change: + +If **your swagger.json is empty**, please check, whether you awai swaggerDoc call in app.ts + +`const swaggerSpec = `**await**` swaggerDoc(options);` + ## Stay in touch * Author - [Raja SIngh](https://www.linkedin.com/in/raja-singh-a097458a/) diff --git a/example/v2/app.ts b/example/v2/app.ts index 519f9f9..6b26072 100644 --- a/example/v2/app.ts +++ b/example/v2/app.ts @@ -11,7 +11,7 @@ const swaggerDefinition = { }, host: `localhost:8000`, // Host (optional) basePath: '/', // Base path (optional) - openapi: '3.0.0', // Swagger version (optional) + swagger: '2.0', // Swagger version (optional) }; // Options for the swagger docs @@ -22,13 +22,12 @@ const options = { // Note that this path is relative to the current directory from which the Node.js is ran, not the application itself. apis: [ './example/v2/routes.ts', - './example/v2/**/*.yaml', - './example/v2/parser.ts' + './example/v2/**/*.yaml' ], }; // Initialize swagger-jsdoc -> returns validated swagger spec in json format -const swaggerSpec = swaggerDoc(options); +const swaggerSpec = await swaggerDoc(options); const app = new Application(); diff --git a/example/v2/parser.ts b/example/v2/parser.ts deleted file mode 100644 index 65216de..0000000 --- a/example/v2/parser.ts +++ /dev/null @@ -1,25 +0,0 @@ -/** - * @swagger-enum - */ -export enum TestingEnum { - VALUE1, - VALUE2, - VALUE3 -} - -/** - * @swagger-enum - */ -export enum TestingEnumWithCustomValues { - VALUE1 = 'val1', - VALUE2 = 'val2', - VALUE3 = 'val3' -} - -export type TestType = string | number; - -export interface TestInterface { - name: string; - type: TestType; - enum: TestingEnum; -} \ No newline at end of file diff --git a/example/v3/app.ts b/example/v3/app.ts new file mode 100644 index 0000000..f557b8c --- /dev/null +++ b/example/v3/app.ts @@ -0,0 +1,45 @@ +import { Application } from "https://deno.land/x/oak/mod.ts"; +import { router } from "./routes.ts"; +import { swaggerDoc } from "../../mod.ts"; + +const swaggerDefinition = { + info: { + // API informations (required) + title: 'Hello World', // Title (required) + version: '1.0.0', // Version (required) + description: 'A sample API', // Description (optional) + }, + openapi: '3.0.0', // openapi version (required) +}; + +// Options for the swagger docs +const options = { + // Import swaggerDefinitions + swaggerDefinition, + // Path to the API docs + // Note that this path is relative to the current directory from which the Node.js is ran, not the application itself. + apis: [ + './example/v3/routes.ts', + './example/v3/**/*.yaml', + './example/v3/enum.ts' + ], +}; + +// Initialize swagger-jsdoc -> returns validated swagger spec in json format +const swaggerSpec = await swaggerDoc(options); + + +const app = new Application(); +app.use(async (context, next) => { + if(context.request.url.pathname === '/swagger.json'){ + context.response.headers.set('Content-Type', 'application/json'); + context.response.status = 200; + context.response.body = swaggerSpec + }else{ + await next(); + } +}); +app.use(router.routes()); +app.use(router.allowedMethods()); + +await app.listen({ port: 8000 }); \ No newline at end of file diff --git a/example/v3/enum.ts b/example/v3/enum.ts new file mode 100644 index 0000000..381d55d --- /dev/null +++ b/example/v3/enum.ts @@ -0,0 +1,35 @@ +/** + * @swagger-enum + */ +export enum EnumOfNumbers { + NOT_FOUND = 404, + SERVER_ERROR = 500, + REDIRECT = 301 +} + +/** + * @swagger-enum + */ +export enum EnumOfStrings { + NOT_FOUND = 'A', + SERVER_ERROR = 'B', + REDIRECT = 'C' +} + +/** + * @swagger-enum + */ +export enum MixedEnum { + NOT_FOUND = 'A', + SERVER_ERROR = 500, + REDIRECT = 'B' +} + +/** + * @swagger-enum + */ +export enum NoCustomValues { + VALUE1, + VALUE2, + VALUE3 +} \ No newline at end of file diff --git a/example/v3/routes.ts b/example/v3/routes.ts new file mode 100644 index 0000000..2cb782a --- /dev/null +++ b/example/v3/routes.ts @@ -0,0 +1,44 @@ +import { Router } from "https://deno.land/x/oak/mod.ts"; + +const books = new Map(); +books.set("1", { + id: "1", + title: "The Hound of the Baskervilles", + author: "Conan Doyle, Arthur", +}); + +const router = new Router(); + + /** + * @swagger + * /: + * get: + * description: Returns the homepage + * responses: + * 200: + * description: hello world + * content: + * application/json: + * schema: + * type: object + * properties: + * value: + * description: Enum usage example + * $ref: '#/components/schemas/NoCustomValues' + */ +router + .get("/", (context) => { + context.response.body = "Hello world!"; + }) + .get("/book", (context) => { + context.response.body = Array.from(books.values()); + }) + .get("/book/:id", (context) => { + if (context.params && context.params.id && books.has(context.params.id)) { + context.response.body = books.get(context.params.id); + } + }); + + export { + router + } \ No newline at end of file diff --git a/lib/helpers/parseApiFileContent.ts b/lib/helpers/parseApiFileContent.ts index d4997cc..b479f15 100644 --- a/lib/helpers/parseApiFileContent.ts +++ b/lib/helpers/parseApiFileContent.ts @@ -11,7 +11,7 @@ import doctrine from 'https://dev.jspm.io/doctrine'; * @returns {{jsdoc: array, yaml: array}} JSDoc comments and Yaml files * @requires doctrine */ -export async function parseApiFileContent(fileContent: any, ext: any) { +export function parseApiFileContent(fileContent: any, ext: any) { const jsDocRegex = /\/\*\*([\s\S]*?)\*\//gm; const yaml = []; const jsDocComments = []; @@ -23,11 +23,9 @@ export async function parseApiFileContent(fileContent: any, ext: any) { if (regexResults) { for (let i = 0; i < regexResults.length; i += 1) { if (regexResults[i].indexOf('@swagger-enum') !== -1) { - console.log("myResult", regexResults[i]); const endOfJsDocComment = fileContent.indexOf(regexResults[i]) + regexResults[i].length; fileContent = fileContent.substring(endOfJsDocComment); const [all, enumName, enumDef] = fileContent.match(/enum\s([^\s]*)\s{([^}]*)}/m); - console.log("enumFound", enumName); if (enumName && enumDef) { const enumItems = enumDef.split(/[\r\n]+/).filter((i: string) => i.trim() !== ''); if (enumItems.length) { @@ -37,8 +35,10 @@ export async function parseApiFileContent(fileContent: any, ext: any) { if (item.indexOf('=') === -1) { itemsString += '* - ' + item.trim().replace(/,$/, '') + '\n'; } else { - type = 'string'; const itemParts = item.split('='); + if (Number.isNaN(Number(itemParts[1].trim().replace(/,$/, '')))) { + type = 'string'; + } itemsString += ' * - ' + itemParts[1].trim().replace(/,$/, '') + '\n'; } }); @@ -50,18 +50,15 @@ export async function parseApiFileContent(fileContent: any, ext: any) { * type: ${type} * enum: ${itemsString} */`; - console.log("final swagger", regexResults[i]); } } } - const jsDocComment = await doctrine.parse(regexResults[i], { unwrap: true }); + const jsDocComment = doctrine.parse(regexResults[i], { unwrap: true }); jsDocComments.push(jsDocComment); } } } - console.log("parseContent", yaml, jsDocComments); - return { yaml, jsdoc: jsDocComments, diff --git a/lib/helpers/specification.ts b/lib/helpers/specification.ts index 42ac0c3..f0a907b 100644 --- a/lib/helpers/specification.ts +++ b/lib/helpers/specification.ts @@ -151,7 +151,6 @@ function organizeSwaggerProperties(swaggerObject : any, pathObject : any, proper * comments */ export function addDataToSwaggerObject(swaggerObject : any, data : any) { - console.log("addData", swaggerObject, data); if (!swaggerObject || !data) { throw new Error('swaggerObject and data are required!'); } diff --git a/lib/helpers/updateSpecificationObject.ts b/lib/helpers/updateSpecificationObject.ts index 0072ab9..70e38a6 100644 --- a/lib/helpers/updateSpecificationObject.ts +++ b/lib/helpers/updateSpecificationObject.ts @@ -9,7 +9,6 @@ import { filterJsDocComments } from './filterJsDocComments.ts'; * @param {object} specification - Specification accumulator. */ export async function updateSpecificationObject(parsedFile :any, specification: any) { - console.log("parsedFile", parsedFile); const {yaml, jsdoc} = await parsedFile; addDataToSwaggerObject(specification, yaml); diff --git a/test.sh b/test.sh index 8093883..d27bdc3 100644 --- a/test.sh +++ b/test.sh @@ -1 +1 @@ -deno test --unstable --allow-read test/test.ts \ No newline at end of file +deno test --unstable --allow-read --no-check test/test.ts \ No newline at end of file diff --git a/test/test.ts b/test/test.ts index 8543228..69a5658 100644 --- a/test/test.ts +++ b/test/test.ts @@ -1,23 +1,43 @@ import { assertEquals } from 'https://deno.land/std/testing/asserts.ts'; import { swaggerDoc } from "../mod.ts"; import { SwaggerJson } from './testData.ts'; +import { SwaggerJsonV3 } from './testDataV3.ts'; -const swaggerDefinition = { + Deno.test('swaggerDoc() v2', async () => { + const swaggerDefinition = { + info: { + title: 'Hello World', + version: '1.0.0', + description: 'A sample API', + }, + host: `localhost:8000`, + basePath: '/', + }; + + const options = { + swaggerDefinition, + apis: ['./example/v2/routes.ts', './example/v2/**/*.yaml'], + }; + + const swaggerSpec = await swaggerDoc(options); + assertEquals(swaggerSpec, JSON.parse(SwaggerJson)); + }); + +Deno.test('swaggerDoc() v3', async () => { + const swaggerDefinition = { info: { title: 'Hello World', version: '1.0.0', description: 'A sample API', }, - host: `localhost:8000`, - basePath: '/', + openapi: '3.0.0', }; - + const options = { swaggerDefinition, - apis: ['./example/v2/routes.ts', './example/v2/**/*.yaml'], + apis: ['./example/v3/routes.ts', './example/v3/enum.ts', './example/v3/**/*.yaml'], }; - Deno.test('swaggerDoc()', async () => { - const swaggerSpec = swaggerDoc(options); - assertEquals(swaggerSpec, JSON.parse(SwaggerJson)); - }); + const swaggerSpec = await swaggerDoc(options); + assertEquals(swaggerSpec, JSON.parse(SwaggerJsonV3)); +}); diff --git a/test/testData.ts b/test/testData.ts index d36c424..c7b3140 100644 --- a/test/testData.ts +++ b/test/testData.ts @@ -1 +1 @@ -export const SwaggerJson: string = `{"info":{"title":"Hello World","version":"1.0.0","description":"A sample API"},"host":"localhost:8000","basePath":"/","swagger":"2.0","paths":{"/":{"get":{"description":"Returns the homepage","responses":{"200":{"description":"hello world"}}}}},"definitions":{},"responses":{},"parameters":{"nestedLvl3":{"name":"nestedLvl3","description":"Parameters nested in folder 3 times","in":"formData","required":true,"type":"string"},"nestedLvl2":{"name":"nestedLvl2","description":"Parameters nested in folder 2 times","in":"formData","required":true,"type":"string"},"nestedLvl1":{"name":"nestedLvl1","description":"Parameters nested in folder 1 time","in":"formData","required":true,"type":"string"},"username":{"name":"username","description":"Username to use for login.","in":"formData","required":true,"type":"string"}},"securityDefinitions":{},"tags":[]}`; \ No newline at end of file +export const SwaggerJson = `{"info":{"title":"Hello World","version":"1.0.0","description":"A sample API"},"host":"localhost:8000","basePath":"/","swagger":"2.0","paths":{"/":{"get":{"description":"Returns the homepage","responses":{"200":{"description":"hello world"}}}}},"definitions":{},"responses":{},"parameters":{"nestedLvl3":{"name":"nestedLvl3","description":"Parameters nested in folder 3 times","in":"formData","required":true,"type":"string"},"nestedLvl2":{"name":"nestedLvl2","description":"Parameters nested in folder 2 times","in":"formData","required":true,"type":"string"},"nestedLvl1":{"name":"nestedLvl1","description":"Parameters nested in folder 1 time","in":"formData","required":true,"type":"string"},"username":{"name":"username","description":"Username to use for login.","in":"formData","required":true,"type":"string"}},"securityDefinitions":{},"tags":[]}`; \ No newline at end of file diff --git a/test/testDataV3.ts b/test/testDataV3.ts new file mode 100644 index 0000000..79fae0b --- /dev/null +++ b/test/testDataV3.ts @@ -0,0 +1 @@ +export const SwaggerJsonV3 = `{"info":{"title":"Hello World","version":"1.0.0","description":"A sample API"},"openapi":"3.0.0","paths":{"/":{"get":{"description":"Returns the homepage","responses":{"200":{"description":"hello world","content":{"application/json":{"schema":{"type":"object","properties":{"value":{"description":"Enum usage example","$ref":"#/components/schemas/NoCustomValues"}}}}}}}}}},"components":{"schemas":{"EnumOfNumbers":{"type":"number","enum":[404,500,301]},"EnumOfStrings":{"type":"string","enum":["A","B","C"]},"MixedEnum":{"type":"string","enum":["A",500,"B"]},"NoCustomValues":{"type":"number","enum":["VALUE1","VALUE2","VALUE3"]}}},"tags":[]}`; \ No newline at end of file From 55d0b3cc305466942cbb21e1309a39abc339ca12 Mon Sep 17 00:00:00 2001 From: michaill Date: Fri, 1 Apr 2022 23:47:54 +0200 Subject: [PATCH 5/6] fix readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 91ef012..5592410 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ const options = { }; // Initialize swagger-jsdoc -> returns validated swagger spec in json format -const swaggerSpec = swaggerDoc(options); +const swaggerSpec = await swaggerDoc(options); app.use(async (context, next) => { if(context.request.url.pathname === '/swagger.json'){ From 5824ae9cc9699412998f6f971356d51892a198de Mon Sep 17 00:00:00 2001 From: michaill Date: Sun, 3 Apr 2022 13:42:01 +0200 Subject: [PATCH 6/6] cleaner code --- lib/helpers/parseApiFileContent.ts | 36 +++------------------- lib/helpers/swaggerEnumParser.ts | 48 ++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 32 deletions(-) create mode 100644 lib/helpers/swaggerEnumParser.ts diff --git a/lib/helpers/parseApiFileContent.ts b/lib/helpers/parseApiFileContent.ts index b479f15..937025e 100644 --- a/lib/helpers/parseApiFileContent.ts +++ b/lib/helpers/parseApiFileContent.ts @@ -1,6 +1,6 @@ import jsYaml from 'https://dev.jspm.io/js-yaml'; import doctrine from 'https://dev.jspm.io/doctrine'; - +import { swaggerEnumParser } from './swaggerEnumParser.ts'; /** * Parse the provided API file content. @@ -22,37 +22,9 @@ export function parseApiFileContent(fileContent: any, ext: any) { const regexResults = fileContent.match(jsDocRegex); if (regexResults) { for (let i = 0; i < regexResults.length; i += 1) { - if (regexResults[i].indexOf('@swagger-enum') !== -1) { - const endOfJsDocComment = fileContent.indexOf(regexResults[i]) + regexResults[i].length; - fileContent = fileContent.substring(endOfJsDocComment); - const [all, enumName, enumDef] = fileContent.match(/enum\s([^\s]*)\s{([^}]*)}/m); - if (enumName && enumDef) { - const enumItems = enumDef.split(/[\r\n]+/).filter((i: string) => i.trim() !== ''); - if (enumItems.length) { - let itemsString = ''; - let type = 'number'; - enumItems.forEach((item: string) => { - if (item.indexOf('=') === -1) { - itemsString += '* - ' + item.trim().replace(/,$/, '') + '\n'; - } else { - const itemParts = item.split('='); - if (Number.isNaN(Number(itemParts[1].trim().replace(/,$/, '')))) { - type = 'string'; - } - itemsString += ' * - ' + itemParts[1].trim().replace(/,$/, '') + '\n'; - } - }); - regexResults[i] = `/** - * @swagger - * components: - * schemas: - * ${enumName}: - * type: ${type} - * enum: -${itemsString} */`; - } - } - } + const { content, jsDoc } = swaggerEnumParser(fileContent, regexResults[i]); + fileContent = content; + regexResults[i] = jsDoc; const jsDocComment = doctrine.parse(regexResults[i], { unwrap: true }); jsDocComments.push(jsDocComment); } diff --git a/lib/helpers/swaggerEnumParser.ts b/lib/helpers/swaggerEnumParser.ts new file mode 100644 index 0000000..20664ca --- /dev/null +++ b/lib/helpers/swaggerEnumParser.ts @@ -0,0 +1,48 @@ +/** + * Parses jsDocs containing @swagger-enum to openapi definition of followed enum + * + * @param content file content + * @param jsDoc matched jsDoc + * @returns { + * jsDoc: string - new jsDoc containing enum definition + * content: string - new content without processed enums + * } + */ +export function swaggerEnumParser(content: string, jsDoc: string): {content: string, jsDoc: string} { + if (jsDoc.indexOf('@swagger-enum') !== -1) { + const endOfJsDocComment = content.indexOf(jsDoc) + jsDoc.length; + content = content.substring(endOfJsDocComment); + const [_all, enumName, enumDef] = content.match(/enum\s([^\s]*)\s{([^}]*)}/m) as RegExpMatchArray; + if (enumName && enumDef) { + const enumItems = enumDef.split(/[\r\n]+/).filter((i: string) => i.trim() !== ''); + if (enumItems.length) { + let itemsString = ''; + let type = 'number'; + enumItems.forEach((item: string) => { + if (item.indexOf('=') === -1) { + itemsString += '* - ' + item.trim().replace(/,$/, '') + '\n'; + } else { + const itemParts = item.split('='); + if (Number.isNaN(Number(itemParts[1].trim().replace(/,$/, '')))) { + type = 'string'; + } + itemsString += ' * - ' + itemParts[1].trim().replace(/,$/, '') + '\n'; + } + }); + return { + content, + jsDoc: `/** + * @swagger + * components: + * schemas: + * ${enumName}: + * type: ${type} + * enum: +${itemsString} */` + }; + } + } + } + // no swagger-enum present + return { content, jsDoc }; +} \ No newline at end of file