From 8de512a20e443e21d94d2441168a8e848d2ff8e8 Mon Sep 17 00:00:00 2001 From: Randolf Tjandra Date: Fri, 6 Sep 2024 10:12:12 -0700 Subject: [PATCH] Add item type for array types and tags for endpoints to overwrite whatever module is (#7) Issue: none Reviewer: wyattmufson --- __tests__/api.test.js | 2 +- src/api/argument.js | 3 +- src/api/http.js | 5 ++- src/index.js | 47 ++++++++++++----------- src/utils/oas.js | 88 ++++++++++++++++++++++++++++++++++++++++--- 5 files changed, 113 insertions(+), 32 deletions(-) diff --git a/__tests__/api.test.js b/__tests__/api.test.js index 3e1c839..d83a562 100644 --- a/__tests__/api.test.js +++ b/__tests__/api.test.js @@ -136,7 +136,7 @@ afterAll(async () => { describe('Using rio.get, rio.put, rio.post, rio.patch, and rio.delete', () => { test('Checking endpoints', async () => { const { routes } = rio.utils.getEndpoints(app, rio.paths); - expect(routes.length).toBe(17); + expect(routes.length).toBe(29); }); test('Get sum', async () => { diff --git a/src/api/argument.js b/src/api/argument.js index acf910a..48e8909 100644 --- a/src/api/argument.js +++ b/src/api/argument.js @@ -1,10 +1,11 @@ class Argument { - constructor(name, type, required = false, description = null, exampleValue = null) { + constructor(name, type, required = false, description = null, exampleValue = null, itemType = undefined) { this.name = name; this.type = type; this.required = required; this.description = description; this.exampleValue = exampleValue || type.example; + this.itemType = itemType; } } diff --git a/src/api/http.js b/src/api/http.js index 851b090..fb04760 100644 --- a/src/api/http.js +++ b/src/api/http.js @@ -8,6 +8,7 @@ const rioExampleResultOfEndpoint = {}; const rioStatusOfEndpoint = {}; const rioAvailabilityOfEndpoint = {}; const rioIgnoreGlobalsForEndpoint = {}; +const rioTagsForEndpoint = {}; function invalidType(providedArg, res) { const result = JSON.stringify({ error: `Argument ${providedArg.name} was not of the specified type ${providedArg.type.name}` }); @@ -88,7 +89,7 @@ function handleListener(handler, method, endpoint, callback) { } } -function addHTTPListener(rio, endpoint, ignoreGlobals, callback, args, description, exampleResult, method, status, availability, routerName = '') { +function addHTTPListener(rio, endpoint, ignoreGlobals, callback, args, description, exampleResult, method, status, availability, routerName = '', tags = []) { const key = `${method}${routerName}${endpoint}`; rioArgsForEndpoint[key] = args; rioTypeOfEndpoint[key] = method; @@ -97,6 +98,7 @@ function addHTTPListener(rio, endpoint, ignoreGlobals, callback, args, descripti rioStatusOfEndpoint[key] = status; rioAvailabilityOfEndpoint[key] = availability; rioIgnoreGlobalsForEndpoint[key] = ignoreGlobals; + rioTagsForEndpoint[key] = tags; let handler = rio.app; @@ -130,4 +132,5 @@ module.exports = { rioStatusOfEndpoint, rioAvailabilityOfEndpoint, rioIgnoreGlobalsForEndpoint, + rioTagsForEndpoint, }; diff --git a/src/index.js b/src/index.js index babe281..664cfa5 100644 --- a/src/index.js +++ b/src/index.js @@ -17,6 +17,7 @@ const { rioStatusOfEndpoint, rioAvailabilityOfEndpoint, rioIgnoreGlobalsForEndpoint, + rioTagsForEndpoint, } = http; const rio = { @@ -38,44 +39,44 @@ rio.router.init = (expressRouter, routerName) => { rio.routers[routerName] = expressRouter; }; -rio.get = (endpoint, callback, args = [], description = null, exampleResult = null, status = Status.live, availability = Availability.public, ignoreGlobals = false) => { - addHTTPListener(rio, endpoint, ignoreGlobals, callback, args, description, exampleResult, 'GET', status, availability); +rio.get = (endpoint, callback, args = [], description = null, exampleResult = null, status = Status.live, availability = Availability.public, ignoreGlobals = false, tags = []) => { + addHTTPListener(rio, endpoint, ignoreGlobals, callback, args, description, exampleResult, 'GET', status, availability, tags); }; -rio.post = (endpoint, callback, args = [], description = null, exampleResult = null, status = Status.live, availability = Availability.public, ignoreGlobals = false) => { - addHTTPListener(rio, endpoint, ignoreGlobals, callback, args, description, exampleResult, 'POST', status, availability); +rio.post = (endpoint, callback, args = [], description = null, exampleResult = null, status = Status.live, availability = Availability.public, ignoreGlobals = false, tags = []) => { + addHTTPListener(rio, endpoint, ignoreGlobals, callback, args, description, exampleResult, 'POST', status, availability, tags); }; -rio.put = (endpoint, callback, args = [], description = null, exampleResult = null, status = Status.live, availability = Availability.public, ignoreGlobals = false) => { - addHTTPListener(rio, endpoint, ignoreGlobals, callback, args, description, exampleResult, 'PUT', status, availability); +rio.put = (endpoint, callback, args = [], description = null, exampleResult = null, status = Status.live, availability = Availability.public, ignoreGlobals = false, tags = []) => { + addHTTPListener(rio, endpoint, ignoreGlobals, callback, args, description, exampleResult, 'PUT', status, availability, tags); }; -rio.patch = (endpoint, callback, args = [], description = null, exampleResult = null, status = Status.live, availability = Availability.public, ignoreGlobals = false) => { - addHTTPListener(rio, endpoint, ignoreGlobals, callback, args, description, exampleResult, 'PATCH', status, availability); +rio.patch = (endpoint, callback, args = [], description = null, exampleResult = null, status = Status.live, availability = Availability.public, ignoreGlobals = false, tags = []) => { + addHTTPListener(rio, endpoint, ignoreGlobals, callback, args, description, exampleResult, 'PATCH', status, availability, tags); }; -rio.delete = (endpoint, callback, args = [], description = null, exampleResult = null, status = Status.live, availability = Availability.public, ignoreGlobals = false) => { - addHTTPListener(rio, endpoint, ignoreGlobals, callback, args, description, exampleResult, 'DELETE', status, availability); +rio.delete = (endpoint, callback, args = [], description = null, exampleResult = null, status = Status.live, availability = Availability.public, ignoreGlobals = false, tags = []) => { + addHTTPListener(rio, endpoint, ignoreGlobals, callback, args, description, exampleResult, 'DELETE', status, availability, tags); }; -rio.router.get = (routerName, endpoint, callback, args = [], description = null, exampleResult = null, status = Status.live, availability = Availability.public, ignoreGlobals = false) => { - addHTTPListener(rio, endpoint, ignoreGlobals, callback, args, description, exampleResult, 'GET', status, availability, routerName); +rio.router.get = (routerName, endpoint, callback, args = [], description = null, exampleResult = null, status = Status.live, availability = Availability.public, ignoreGlobals = false, tags = []) => { + addHTTPListener(rio, endpoint, ignoreGlobals, callback, args, description, exampleResult, 'GET', status, availability, routerName, tags); }; -rio.router.post = (routerName, endpoint, callback, args = [], description = null, exampleResult = null, status = Status.live, availability = Availability.public, ignoreGlobals = false) => { - addHTTPListener(rio, endpoint, ignoreGlobals, callback, args, description, exampleResult, 'POST', status, availability, routerName); +rio.router.post = (routerName, endpoint, callback, args = [], description = null, exampleResult = null, status = Status.live, availability = Availability.public, ignoreGlobals = false, tags = []) => { + addHTTPListener(rio, endpoint, ignoreGlobals, callback, args, description, exampleResult, 'POST', status, availability, routerName, tags); }; -rio.router.put = (routerName, endpoint, callback, args = [], description = null, exampleResult = null, status = Status.live, availability = Availability.public, ignoreGlobals = false) => { - addHTTPListener(rio, endpoint, ignoreGlobals, callback, args, description, exampleResult, 'PUT', status, availability, routerName); +rio.router.put = (routerName, endpoint, callback, args = [], description = null, exampleResult = null, status = Status.live, availability = Availability.public, ignoreGlobals = false, tags = []) => { + addHTTPListener(rio, endpoint, ignoreGlobals, callback, args, description, exampleResult, 'PUT', status, availability, routerName, tags); }; -rio.router.patch = (routerName, endpoint, callback, args = [], description = null, exampleResult = null, status = Status.live, availability = Availability.public, ignoreGlobals = false) => { - addHTTPListener(rio, endpoint, ignoreGlobals, callback, args, description, exampleResult, 'PATCH', status, availability, routerName); +rio.router.patch = (routerName, endpoint, callback, args = [], description = null, exampleResult = null, status = Status.live, availability = Availability.public, ignoreGlobals = false, tags = []) => { + addHTTPListener(rio, endpoint, ignoreGlobals, callback, args, description, exampleResult, 'PATCH', status, availability, routerName, tags); }; -rio.router.delete = (routerName, endpoint, callback, args = [], description = null, exampleResult = null, status = Status.live, availability = Availability.public, ignoreGlobals = false) => { - addHTTPListener(rio, endpoint, ignoreGlobals, callback, args, description, exampleResult, 'DELETE', status, availability, routerName); +rio.router.delete = (routerName, endpoint, callback, args = [], description = null, exampleResult = null, status = Status.live, availability = Availability.public, ignoreGlobals = false, tags = []) => { + addHTTPListener(rio, endpoint, ignoreGlobals, callback, args, description, exampleResult, 'DELETE', status, availability, routerName, tags); }; rio.writeREADME = (path, isPublic = true) => { @@ -91,7 +92,7 @@ rio.oasGenerate = (path, isPublic = true) => { if (pathToUse == null) { pathToUse = process.cwd(); } - utils.oasGenerate(pathToUse, isPublic, rio.paths, rio.app, rio.appName, rio.globalArgs, rioArgsForEndpoint, rioTypeOfEndpoint, rioDescriptionOfEndpoint, rioExampleResultOfEndpoint, rioStatusOfEndpoint, rioAvailabilityOfEndpoint, rioIgnoreGlobalsForEndpoint); + utils.oasGenerate(pathToUse, isPublic, rio.paths, rio.app, rio.appName, rio.globalArgs, rioArgsForEndpoint, rioTypeOfEndpoint, rioDescriptionOfEndpoint, rioExampleResultOfEndpoint, rioStatusOfEndpoint, rioAvailabilityOfEndpoint, rioIgnoreGlobalsForEndpoint, rioTagsForEndpoint); }; rio.init = (app, name = null, globalArgs = []) => { @@ -116,14 +117,14 @@ rio.RequiredString = (name, description = null, exampleValue = null) => new rio. rio.RequiredFloat = (name, description = null, exampleValue = null) => new rio.Argument(name, rio.ArgumentType.Float, true, description, exampleValue); rio.RequiredBoolean = (name, description = null, exampleValue = null) => new rio.Argument(name, rio.ArgumentType.Boolean, true, description, exampleValue); rio.RequiredMap = (name, description = null, exampleValue = null) => new rio.Argument(name, rio.ArgumentType.Map, true, description, exampleValue); -rio.RequiredArray = (name, description = null, exampleValue = null) => new rio.Argument(name, rio.ArgumentType.Array, true, description, exampleValue); +rio.RequiredArray = (name, description = null, exampleValue = null, itemType = undefined) => new rio.Argument(name, rio.ArgumentType.Array, true, description, exampleValue, itemType); rio.OptionalInteger = (name, description = null, exampleValue = null) => new rio.Argument(name, rio.ArgumentType.Integer, false, description, exampleValue); rio.OptionalString = (name, description = null, exampleValue = null) => new rio.Argument(name, rio.ArgumentType.String, false, description, exampleValue); rio.OptionalFloat = (name, description = null, exampleValue = null) => new rio.Argument(name, rio.ArgumentType.Float, false, description, exampleValue); rio.OptionalBoolean = (name, description = null, exampleValue = null) => new rio.Argument(name, rio.ArgumentType.Boolean, false, description, exampleValue); rio.OptionalMap = (name, description = null, exampleValue = null) => new rio.Argument(name, rio.ArgumentType.Map, false, description, exampleValue); -rio.OptionalArray = (name, description = null, exampleValue = null) => new rio.Argument(name, rio.ArgumentType.Array, false, description, exampleValue); +rio.OptionalArray = (name, description = null, exampleValue = null, itemType = undefined) => new rio.Argument(name, rio.ArgumentType.Array, false, description, exampleValue, itemType); rio.rInt = rio.RequiredInteger; rio.rStr = rio.RequiredString; diff --git a/src/utils/oas.js b/src/utils/oas.js index 7116d43..c4d2c8e 100644 --- a/src/utils/oas.js +++ b/src/utils/oas.js @@ -8,7 +8,20 @@ const { isInMiscModule, } = require('./rc'); -function writeRoutes(oas, routes, globalArgs, moduleForEndpoints, rioExampleResultOfEndpoint, errorExample, rioTypeOfEndpoint, rioDescriptionOfEndpoint, rioStatusOfEndpoint, rioArgsForEndpoint, rioIgnoreGlobalsForEndpoint) { +function writeRoutes( + oas, + routes, + globalArgs, + moduleForEndpoints, + rioExampleResultOfEndpoint, + errorExample, + rioTypeOfEndpoint, + rioDescriptionOfEndpoint, + rioStatusOfEndpoint, + rioArgsForEndpoint, + rioIgnoreGlobalsForEndpoint, + rioTagsForEndpoint, +) { const endpointCount = routes.length; routes.sort((a, b) => { if (formatEndpoint(a) < formatEndpoint(b)) { @@ -54,6 +67,12 @@ function writeRoutes(oas, routes, globalArgs, moduleForEndpoints, rioExampleResu required: argument.required, schema: { type: argument.type.oasType, + ...(argument.type.oasType === 'array' && { + items: { + type: argument.itemType, + }, + }), + example: argument.exampleValue, }, }; @@ -66,6 +85,11 @@ function writeRoutes(oas, routes, globalArgs, moduleForEndpoints, rioExampleResu properties[argument.name] = { type: argument.type.oasType, + ...(argument.type.oasType === 'array' && { + items: { + type: argument.itemType, + }, + }), description: argument.description, }; @@ -101,7 +125,11 @@ function writeRoutes(oas, routes, globalArgs, moduleForEndpoints, rioExampleResu oas.paths[route][method].deprecated = status.name === 'deprecated'; - oas.paths[route][method].tags = [module]; + if (rioTagsForEndpoint[endpoint].length > 0) { + oas.paths[route][method].tags = rioTagsForEndpoint[endpoint]; + } else { + oas.paths[route][method].tags = [module]; + } if (method === 'get' || method === 'delete') { oas.paths[route][method].parameters = parameters; @@ -232,8 +260,29 @@ function writeRoutes(oas, routes, globalArgs, moduleForEndpoints, rioExampleResu } } -function oasGenerate(path, isPublic, paths, app, appName, globalArgs, rioArgsForEndpoint, rioTypeOfEndpoint, rioDescriptionOfEndpoint, rioExampleResultOfEndpoint, rioStatusOfEndpoint, rioAvailabilityOfEndpoint, rioIgnoreGlobalsForEndpoint) { - const { moduleForEndpoints, modules, routes } = router.getEndpoints(app, paths, rioStatusOfEndpoint, rioAvailabilityOfEndpoint, isPublic); +function oasGenerate( + path, + isPublic, + paths, + app, + appName, + globalArgs, + rioArgsForEndpoint, + rioTypeOfEndpoint, + rioDescriptionOfEndpoint, + rioExampleResultOfEndpoint, + rioStatusOfEndpoint, + rioAvailabilityOfEndpoint, + rioIgnoreGlobalsForEndpoint, + rioTagsForEndpoint, +) { + const { moduleForEndpoints, modules, routes } = router.getEndpoints( + app, + paths, + rioStatusOfEndpoint, + rioAvailabilityOfEndpoint, + isPublic, + ); routes.sort(); const rc = getRioRC(path); @@ -323,10 +372,37 @@ function oasGenerate(path, isPublic, paths, app, appName, globalArgs, rioArgsFor for (let i = 0; i < modules.length; i += 1) { const module = modules[i]; const moduleRoutes = routes.filter((route) => isInModule(route, module)); - writeRoutes(oas, moduleRoutes, globalArgs, moduleForEndpoints, rioExampleResultOfEndpoint, errorExample, rioTypeOfEndpoint, rioDescriptionOfEndpoint, rioStatusOfEndpoint, rioArgsForEndpoint, rioIgnoreGlobalsForEndpoint); + writeRoutes( + oas, + moduleRoutes, + globalArgs, + moduleForEndpoints, + rioExampleResultOfEndpoint, + errorExample, + rioTypeOfEndpoint, + rioDescriptionOfEndpoint, + rioStatusOfEndpoint, + rioArgsForEndpoint, + rioIgnoreGlobalsForEndpoint, + rioTagsForEndpoint, + ); } + // This just means that the route is one part like /foo const miscRoutes = routes.filter((route) => isInMiscModule(route)); - writeRoutes(oas, miscRoutes, globalArgs, moduleForEndpoints, rioExampleResultOfEndpoint, errorExample, rioTypeOfEndpoint, rioDescriptionOfEndpoint, rioStatusOfEndpoint, rioArgsForEndpoint, rioIgnoreGlobalsForEndpoint); + writeRoutes( + oas, + miscRoutes, + globalArgs, + moduleForEndpoints, + rioExampleResultOfEndpoint, + errorExample, + rioTypeOfEndpoint, + rioDescriptionOfEndpoint, + rioStatusOfEndpoint, + rioArgsForEndpoint, + rioIgnoreGlobalsForEndpoint, + rioTagsForEndpoint, + ); const fileName = `${isPublic ? 'public-' : ''}swagger.json`; const formatted = JSON.stringify(oas, null, 2);