UNPKG

@whook/whook

Version:

Build strong and efficient REST web services.

162 lines (147 loc) 4.29 kB
import { autoService, location } from 'knifecycle'; import { type WhookRouteHandler, type WhookRouteDefinition, } from '../types/routes.js'; import { ensureResolvedObject, pathItemToOperationMap, type OpenAPIExtension, type OpenAPIParameter, type OpenAPIReference, type OpenAPI, isValidOpenAPIPath, isValidOpenAPIMethod, } from 'ya-open-api-types'; import { type Jsonify } from 'type-fest'; async function removeMutedParameters( API: OpenAPI, parameters: ( | OpenAPIReference<OpenAPIParameter<unknown, OpenAPIExtension>> | OpenAPIParameter<unknown, OpenAPIExtension> )[], mutedParameters: string[], ) { const filteredParameters: typeof parameters = []; for (const parameter of parameters) { const dereferencedParameter = await ensureResolvedObject(API, parameter); if (mutedParameters.includes(dereferencedParameter.name)) { continue; } filteredParameters.push(parameter); } return filteredParameters; } /* Architecture Note #3: the routes Routes are services that provide a definition and implements API routes. */ export const definition = { path: '/openAPI', method: 'get', operation: { operationId: 'getOpenAPI', summary: 'Get the API documentation.', tags: ['system'], // Here we do not declare the OpenAPI JSON Schema to KISS responses: { '200': { description: 'Provides the private Open API documentation', content: { 'application/json': { schema: { type: 'object', }, }, }, }, }, }, } as const satisfies WhookRouteDefinition; async function initGetOpenAPI({ API }: { API: OpenAPI }) { const getOpenAPI = async ({ query: { mutedMethods = ['options'], mutedParameters = [], mutedTags = [] }, options: { authenticated = false } = { authenticated: false }, }: { query: { mutedMethods?: string[]; mutedParameters?: string[]; mutedTags?: string[]; }; options?: { authenticated?: boolean; }; }) => { const tagIsPresent: Record<string, boolean> = {}; const newPaths: NonNullable<(typeof API)['paths']> = {}; for (const [path, pathItem] of Object.entries(API.paths || {})) { if (!isValidOpenAPIPath(path)) { continue; } for (const [method, operation] of Object.entries( pathItemToOperationMap(pathItem || {}), )) { if (!isValidOpenAPIMethod(method)) { continue; } if (mutedMethods.includes(method)) { continue; } if (operation.tags?.every((tag) => mutedTags.includes(tag))) { continue; } if ( typeof operation['x-whook'] === 'object' && operation['x-whook'] && 'private' in operation['x-whook'] && operation['x-whook'].private && !authenticated ) { continue; } for (const tag of operation?.tags || []) { tagIsPresent[tag] = true; } newPaths[path] = { ...newPaths[path], ...(API?.paths?.[path]?.parameters ? { parameters: await removeMutedParameters( API, API.paths[path].parameters, mutedParameters, ), } : {}), [method]: { ...(API?.paths?.[path]?.[method] || {}), ...(operation.parameters && operation.parameters.length && { parameters: await removeMutedParameters( API, operation.parameters, mutedParameters, ), }), ...(authenticated ? {} : { 'x-whook': undefined, }), }, } as NonNullable<(typeof API)['paths']>; } } const CLEANED_API: OpenAPI = { ...API, paths: newPaths, tags: API.tags ? API.tags.filter((tag) => tagIsPresent[tag.name]) : [], }; return { status: 200, body: CLEANED_API as Jsonify<OpenAPI>, }; }; return getOpenAPI satisfies WhookRouteHandler; } export default location(autoService(initGetOpenAPI), import.meta.url);