From 6597207b7a0e177b81f7d0a18e0c79ba67e38f5d Mon Sep 17 00:00:00 2001 From: Nicolas Froidure Date: Tue, 7 Nov 2023 14:11:19 +0100 Subject: [PATCH] feat(@whook/whook): allow to filter the API fix #170 --- packages/whook-example/src/index.test.ts | 8 +- .../services/FILTER_API_DEFINITION.test.ts | 86 ++++++++++ ...R_API_TAGS.ts => FILTER_API_DEFINITION.ts} | 24 ++- .../src/services/FILTER_API_TAGS.test.ts | 48 ------ .../FILTER_API_TAGS.test.ts.snap | 19 --- packages/whook-example/src/whook.d.ts | 4 +- packages/whook/src/index.ts | 1 + .../src/services/API_DEFINITIONS.test.ts | 161 +++++++++--------- .../whook/src/services/API_DEFINITIONS.ts | 32 ++-- 9 files changed, 206 insertions(+), 177 deletions(-) create mode 100644 packages/whook-example/src/services/FILTER_API_DEFINITION.test.ts rename packages/whook-example/src/services/{FILTER_API_TAGS.ts => FILTER_API_DEFINITION.ts} (60%) delete mode 100644 packages/whook-example/src/services/FILTER_API_TAGS.test.ts delete mode 100644 packages/whook-example/src/services/__snapshots__/FILTER_API_TAGS.test.ts.snap diff --git a/packages/whook-example/src/index.test.ts b/packages/whook-example/src/index.test.ts index 1ec48da2..d6e973a9 100644 --- a/packages/whook-example/src/index.test.ts +++ b/packages/whook-example/src/index.test.ts @@ -133,7 +133,7 @@ describe('runServer', () => { "💿 - Loading "API" initializer from "/home/whoiam/projects/whook/packages/whook-example/src/services/API.ts".", ], [ - "💿 - Loading "FILTER_API_TAGS" initializer from "/home/whoiam/projects/whook/packages/whook-example/src/services/FILTER_API_TAGS.ts".", + "💿 - Loading "FILTER_API_DEFINITION" initializer from "/home/whoiam/projects/whook/packages/whook-example/src/services/FILTER_API_DEFINITION.ts".", ], [ "💿 - Loading "MECHANISMS" initializer from "/home/whoiam/projects/whook/packages/whook-example/src/services/MECHANISMS.ts".", @@ -181,7 +181,7 @@ describe('runServer', () => { "💿 - Service "API" found in "/home/whoiam/projects/whook/packages/whook-example/src/services/API.ts".", ], [ - "💿 - Service "FILTER_API_TAGS" found in "/home/whoiam/projects/whook/packages/whook-example/src/services/FILTER_API_TAGS.ts".", + "💿 - Service "FILTER_API_DEFINITION" found in "/home/whoiam/projects/whook/packages/whook-example/src/services/FILTER_API_DEFINITION.ts".", ], [ "💿 - Service "MECHANISMS" found in "/home/whoiam/projects/whook/packages/whook-example/src/services/MECHANISMS.ts".", @@ -580,7 +580,7 @@ describe('runServer', () => { "🛂 - Dynamic import of "/home/whoiam/projects/whook/packages/whook-example/src/services/API.ts".", ], [ - "🛂 - Dynamic import of "/home/whoiam/projects/whook/packages/whook-example/src/services/FILTER_API_TAGS.ts".", + "🛂 - Dynamic import of "/home/whoiam/projects/whook/packages/whook-example/src/services/FILTER_API_DEFINITION.ts".", ], [ "🛂 - Dynamic import of "/home/whoiam/projects/whook/packages/whook-example/src/services/MECHANISMS.ts".", @@ -640,7 +640,7 @@ describe('runServer', () => { "🛂 - Resolving "/home/whoiam/projects/whook/packages/whook-example/src/services/API.ts".", ], [ - "🛂 - Resolving "/home/whoiam/projects/whook/packages/whook-example/src/services/FILTER_API_TAGS.ts".", + "🛂 - Resolving "/home/whoiam/projects/whook/packages/whook-example/src/services/FILTER_API_DEFINITION.ts".", ], [ "🛂 - Resolving "/home/whoiam/projects/whook/packages/whook-example/src/services/MECHANISMS.ts".", diff --git a/packages/whook-example/src/services/FILTER_API_DEFINITION.test.ts b/packages/whook-example/src/services/FILTER_API_DEFINITION.test.ts new file mode 100644 index 00000000..2bf816fc --- /dev/null +++ b/packages/whook-example/src/services/FILTER_API_DEFINITION.test.ts @@ -0,0 +1,86 @@ +import { describe, test, beforeEach, jest, expect } from '@jest/globals'; +import initFilterAPIDefinition from './FILTER_API_DEFINITION.js'; +import type { LogService } from 'common-services'; + +describe('initFilterAPIDefinition', () => { + describe('should work', () => { + const log = jest.fn(); + + beforeEach(() => { + log.mockClear(); + }); + + test('with empty ENV', async () => { + const FILTER_API_DEFINITION = await initFilterAPIDefinition({ + ENV: {}, + log, + }); + + expect( + FILTER_API_DEFINITION({ + path: '/test', + method: 'get', + operation: { + operationId: 'test', + parameters: [], + tags: ['test'], + responses: {}, + }, + }), + ).toBeFalsy(); + expect({ + logCalls: log.mock.calls, + }).toMatchInlineSnapshot(` +{ + "logCalls": [], +} +`); + }); + + test('with some tags in ENV', async () => { + const FILTER_API_DEFINITION = await initFilterAPIDefinition({ + ENV: { + FILTER_API_TAGS: 'test,test2', + }, + log, + }); + + expect( + FILTER_API_DEFINITION({ + path: '/test', + method: 'get', + operation: { + operationId: 'test', + parameters: [], + tags: ['test'], + responses: {}, + }, + }), + ).toBeFalsy(); + expect( + FILTER_API_DEFINITION({ + path: '/test', + method: 'get', + operation: { + operationId: 'test3', + parameters: [], + tags: ['test3'], + responses: {}, + }, + }), + ).toBeTruthy(); + expect({ + logCalls: log.mock.calls, + }).toMatchInlineSnapshot(` +{ + "logCalls": [ + [ + "warning", + "⏳ - Filtering API with (test,test2) tags!", + ], + ], +} +`); + }); + }); +}); diff --git a/packages/whook-example/src/services/FILTER_API_TAGS.ts b/packages/whook-example/src/services/FILTER_API_DEFINITION.ts similarity index 60% rename from packages/whook-example/src/services/FILTER_API_TAGS.ts rename to packages/whook-example/src/services/FILTER_API_DEFINITION.ts index 908e27ca..7a1ff68f 100644 --- a/packages/whook-example/src/services/FILTER_API_TAGS.ts +++ b/packages/whook-example/src/services/FILTER_API_DEFINITION.ts @@ -1,10 +1,17 @@ import { name, autoService } from 'knifecycle'; import { noop, identity } from '@whook/whook'; import type { LogService } from 'common-services'; +import type { + WhookAPIHandlerDefinition, + WhookAPIDefinitionFilter, +} from '@whook/whook'; -export default name('FILTER_API_TAGS', autoService(initFilterAPITags)); +export default name( + 'FILTER_API_DEFINITION', + autoService(initFilterAPIDefinition), +); -export type FilterAPITagsEnvVars = { +export type FilterAPIDefinitionEnvVars = { FILTER_API_TAGS?: string; }; @@ -21,20 +28,25 @@ For example, to create a server with only `system` and FILTER_API_TAGS=system,example npm start ``` */ -async function initFilterAPITags({ +async function initFilterAPIDefinition({ ENV, log = noop, }: { - ENV: FilterAPITagsEnvVars; + ENV: FilterAPIDefinitionEnvVars; log: LogService; -}): Promise { +}): Promise { const FILTER_API_TAGS = (ENV.FILTER_API_TAGS || '') .split(',') .filter(identity); if (FILTER_API_TAGS.length > 0) { log('warning', `⏳ - Filtering API with (${FILTER_API_TAGS}) tags!`); + return (definition: WhookAPIHandlerDefinition) => { + return !FILTER_API_TAGS.some((tag) => + (definition.operation.tags || []).includes(tag), + ); + }; } - return FILTER_API_TAGS; + return () => false; } diff --git a/packages/whook-example/src/services/FILTER_API_TAGS.test.ts b/packages/whook-example/src/services/FILTER_API_TAGS.test.ts deleted file mode 100644 index fa398ba9..00000000 --- a/packages/whook-example/src/services/FILTER_API_TAGS.test.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { describe, test, beforeEach, jest, expect } from '@jest/globals'; -import initFilterAPITags from './FILTER_API_TAGS.js'; -import type { LogService } from 'common-services'; - -describe('initFilterAPITags', () => { - describe('should work', () => { - const log = jest.fn(); - - beforeEach(() => { - log.mockClear(); - }); - - test('with not ENV', async () => { - const FILTER_API_TAGS = await initFilterAPITags({ - ENV: {}, - log, - }); - - expect(FILTER_API_TAGS).toMatchInlineSnapshot(`[]`); - expect({ - logCalls: log.mock.calls, - }).toMatchSnapshot(); - }); - - test('with empty ENV', async () => { - const FILTER_API_TAGS = await initFilterAPITags({ - ENV: {}, - log, - }); - - expect(FILTER_API_TAGS).toMatchInlineSnapshot(`[]`); - expect({ - logCalls: log.mock.calls, - }).toMatchSnapshot(); - }); - test('with some tags in ENV', async () => { - const FILTER_API_TAGS = await initFilterAPITags({ - ENV: {}, - log, - }); - - expect(FILTER_API_TAGS).toMatchInlineSnapshot(`[]`); - expect({ - logCalls: log.mock.calls, - }).toMatchSnapshot(); - }); - }); -}); diff --git a/packages/whook-example/src/services/__snapshots__/FILTER_API_TAGS.test.ts.snap b/packages/whook-example/src/services/__snapshots__/FILTER_API_TAGS.test.ts.snap deleted file mode 100644 index ae3f1f94..00000000 --- a/packages/whook-example/src/services/__snapshots__/FILTER_API_TAGS.test.ts.snap +++ /dev/null @@ -1,19 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`initFilterAPITags should work with empty ENV 2`] = ` -{ - "logCalls": [], -} -`; - -exports[`initFilterAPITags should work with not ENV 2`] = ` -{ - "logCalls": [], -} -`; - -exports[`initFilterAPITags should work with some tags in ENV 2`] = ` -{ - "logCalls": [], -} -`; diff --git a/packages/whook-example/src/whook.d.ts b/packages/whook-example/src/whook.d.ts index 8a218619..8facf1e1 100644 --- a/packages/whook-example/src/whook.d.ts +++ b/packages/whook-example/src/whook.d.ts @@ -15,7 +15,7 @@ import type { APIConfig } from './services/API.js'; import type { JWTServiceConfig } from 'jwt-service'; import type { BaseAppEnvVars } from 'application-services'; import type { JWTEnvVars } from 'jwt-service'; -import type { FilterAPITagsEnvVars } from './services/FILTER_API_TAGS.ts'; +import type { FilterAPIDefinitionEnvVars } from './services/FILTER_API_DEFINITION.ts'; declare module 'application-services' { // Eventually override the process env type here @@ -23,7 +23,7 @@ declare module 'application-services' { extends BaseAppEnvVars, WhookBaseEnv, JWTEnvVars, - FilterAPITagsEnvVars, + FilterAPIDefinitionEnvVars, WhookSwaggerUIEnv { DRY_RUN?: string; } diff --git a/packages/whook/src/index.ts b/packages/whook/src/index.ts index b3575e98..d05aaa6d 100644 --- a/packages/whook/src/index.ts +++ b/packages/whook/src/index.ts @@ -43,6 +43,7 @@ export { DEFAULT_IGNORED_FILES_SUFFIXES, DEFAULT_REDUCED_FILES_SUFFIXES, type WhookAPIDefinitionsConfig, + type WhookAPIDefinitionFilter, } from './services/API_DEFINITIONS.js'; import initLoggerService from './services/logger.js'; import initExitService from './services/exit.js'; diff --git a/packages/whook/src/services/API_DEFINITIONS.test.ts b/packages/whook/src/services/API_DEFINITIONS.test.ts index 81e710b9..bf24f779 100644 --- a/packages/whook/src/services/API_DEFINITIONS.test.ts +++ b/packages/whook/src/services/API_DEFINITIONS.test.ts @@ -924,7 +924,8 @@ describe('initAPIDefinitions', () => { const API_DEFINITIONS = await initAPIDefinitions({ PROJECT_SRC, - FILTER_API_TAGS: ['user'], + FILTER_API_DEFINITION: (definition) => + !(definition.operation.tags || []).includes('user'), log, readDir, importer, @@ -936,94 +937,94 @@ describe('initAPIDefinitions', () => { readDirCalls: readDir.mock.calls, importerCalls: importer.mock.calls, }).toMatchInlineSnapshot(` - { - "API_DEFINITIONS": { - "components": { - "headers": {}, - "parameters": { - "userId": { - "in": "path", - "name": "userId", - "schema": { - "type": "number", - }, - }, - }, - "requestBodies": {}, - "responses": {}, - "schemas": { - "User": { - "properties": { - "name": { - "type": "string", - }, - }, - "type": "object", +{ + "API_DEFINITIONS": { + "components": { + "headers": {}, + "parameters": { + "userId": { + "in": "path", + "name": "userId", + "schema": { + "type": "number", + }, + }, + }, + "requestBodies": {}, + "responses": {}, + "schemas": { + "User": { + "properties": { + "name": { + "type": "string", + }, + }, + "type": "object", + }, + }, + }, + "paths": { + "/users/{userId}": { + "put": { + "operationId": "putUser", + "parameters": [ + { + "$ref": "#/components/parameters/userId", + }, + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/User", }, }, }, - "paths": { - "/users/{userId}": { - "put": { - "operationId": "putUser", - "parameters": [ - { - "$ref": "#/components/parameters/userId", - }, - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/User", - }, - }, - }, - }, - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/User", - }, - }, - }, - "description": "The user", - }, + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/User", }, - "tags": [ - "user", - ], }, }, + "description": "The user", }, }, - "importerCalls": [ - [ - "/home/whoiam/project/src/handlers/getUser.js", - ], - [ - "/home/whoiam/project/src/handlers/putUser.js", - ], - ], - "logCalls": [ - [ - "debug", - "🈁 - Generating the API_DEFINITIONS", - ], - [ - "debug", - "⏳ - Ignored handler "getUser" via its tags ("other" not found in "user")!", - ], + "tags": [ + "user", ], - "readDirCalls": [ - [ - "/home/whoiam/project/src/handlers", - ], - ], - } - `); + }, + }, + }, + }, + "importerCalls": [ + [ + "/home/whoiam/project/src/handlers/getUser.js", + ], + [ + "/home/whoiam/project/src/handlers/putUser.js", + ], + ], + "logCalls": [ + [ + "debug", + "🈁 - Generating the API_DEFINITIONS", + ], + [ + "debug", + "⏳ - Ignored handler "getUser" via the API definition filter!", + ], + ], + "readDirCalls": [ + [ + "/home/whoiam/project/src/handlers", + ], + ], +} +`); }); }); }); diff --git a/packages/whook/src/services/API_DEFINITIONS.ts b/packages/whook/src/services/API_DEFINITIONS.ts index 397adbf5..be32ea4f 100644 --- a/packages/whook/src/services/API_DEFINITIONS.ts +++ b/packages/whook/src/services/API_DEFINITIONS.ts @@ -32,7 +32,7 @@ export type WhookAPIDefinitionsConfig = { REDUCED_FILES_SUFFIXES?: string[]; IGNORED_FILES_SUFFIXES?: string[]; IGNORED_FILES_PREFIXES?: string[]; - FILTER_API_TAGS?: string[]; + FILTER_API_DEFINITION?: WhookAPIDefinitionFilter; }; export type WhookAPIDefinitionsDependencies = WhookAPIDefinitionsConfig & { @@ -126,6 +126,12 @@ export interface WhookAPIRequestBodyDefinition { requestBody: OpenAPIV3.RequestBodyObject | OpenAPIV3.ReferenceObject; } +export interface WhookAPIDefinitionFilter< + T extends Record = Record, +> { + (definition: WhookAPIHandlerDefinition): boolean; +} + export interface WhookAPIHandlerModule { [name: string]: | WhookAPISchemaDefinition @@ -137,6 +143,8 @@ export interface WhookAPIHandlerModule { definition: WhookAPIHandlerDefinition; } +export const DEFAULT_API_DEFINITION_FILTER = () => false; + export default name('API_DEFINITIONS', autoService(initAPIDefinitions)); /** @@ -151,9 +159,8 @@ export default name('API_DEFINITIONS', autoService(initAPIDefinitions)); * The files suffixes the autoloader must ignore * @param {Object} [services.IGNORED_FILES_PREFIXES] * The files prefixes the autoloader must ignore - * @param {Object} [services.FILTER_API_TAGS] - * Allows to only keep the endpoints taggeds with - * the given tags + * @param {Object} [services.FILTER_API_DEFINITION] + * Allows to filter endpoints if the custom function returns true * @param {Object} services.importer * A service allowing to dynamically import ES modules * @param {Object} [services.log=noop] @@ -167,7 +174,7 @@ async function initAPIDefinitions({ IGNORED_FILES_SUFFIXES = DEFAULT_IGNORED_FILES_SUFFIXES, IGNORED_FILES_PREFIXES = DEFAULT_IGNORED_FILES_PREFIXES, REDUCED_FILES_SUFFIXES = DEFAULT_REDUCED_FILES_SUFFIXES, - FILTER_API_TAGS = [], + FILTER_API_DEFINITION = DEFAULT_API_DEFINITION_FILTER, importer, log = noop, readDir = _readDir, @@ -273,21 +280,10 @@ async function initAPIDefinitions({ return paths; } - const operationTags = - (definition && definition.operation && definition.operation.tags) || - []; - - if ( - FILTER_API_TAGS.length > 0 && - operationTags.every((tag) => !FILTER_API_TAGS.includes(tag)) - ) { + if (FILTER_API_DEFINITION(definition)) { log( 'debug', - `⏳ - Ignored handler "${ - definition.operation.operationId - }" via its tags ("${operationTags.join( - ',', - )}" not found in "${FILTER_API_TAGS.join(',')}")!`, + `⏳ - Ignored handler "${definition.operation.operationId}" via the API definition filter!`, ); return paths; }