UNPKG

@whook/whook

Version:

Build strong and efficient REST web services.

147 lines (146 loc) 5.78 kB
import standaloneCode from 'ajv/dist/standalone/index.js'; import { autoService, location } from 'knifecycle'; import { noop } from '../libs/utils.js'; import { Ajv2020 } from 'ajv/dist/2020.js'; import addAJVFormats from 'ajv-formats'; import { collectAPISchemas, } from 'ya-open-api-types'; import { createHash } from '../libs/hash.js'; import { extractAPISecurityParametersSchemas } from '../libs/validation.js'; /* Architecture Note #2.11.2.1: Schema validators Maintain a single place for JSON schema validation since it may repeat for several routes. Also warrantying that the same schema leads to the same reference for the Siso router parameters uniqueness checks. */ export const DEFAULT_SCHEMA_VALIDATORS_OPTIONS = { lazy: false, dedupe: false, hashLength: 16, buildSchemas: false, }; /** * Initialize the schema validator service for * application schemas validation. This central * place is aimed to compile schemas once and * use them many times. * @param {Object} services * The service dependencies * @param {Object} [services.SCHEMA_VALIDATORS_OPTIONS={}] * Options for the schema validators registry * @param {Object} [services.ENV={}] * An optional environment object * @param {Object} [services.log=noop] * An optional logging service * @param {Object} services.API * A valid Open API file * @return {Promise<Number>} * A promise of a schema validators registry */ async function initSchemaValidators({ DEBUG_NODE_ENVS, SCHEMA_VALIDATORS_OPTIONS = DEFAULT_SCHEMA_VALIDATORS_OPTIONS, API, ENV, log = noop, }) { log('warning', `🖃 - Initializing the validators service.`); const validatorsMap = {}; const ajv = new Ajv2020({ verbose: DEBUG_NODE_ENVS.includes(ENV.NODE_ENV), strict: true, logger: { log: (...args) => log('debug', ...args), warn: (...args) => log('warning', ...args), error: (...args) => log('error', ...args), }, }); addAJVFormats.default(ajv); const schemas = collectAPISchemas(API); for (const schema of schemas) { if ('components' in schema.location && schema.location.components === 'schemas') { const $ref = '#/components/schemas/' + schema.location.schemaName; ajv.addSchema(schema.schema, $ref); } } if (!SCHEMA_VALIDATORS_OPTIONS.lazy) { for (const schema of schemas) { if ('components' in schema.location && schema.location.components === 'schemas') { const $ref = '#/components/schemas/' + schema.location.schemaName; validatorsMap[$ref] = ajv.getSchema($ref); } } } return (schema) => { if (typeof schema === 'object' && '$ref' in schema && typeof schema.$ref === 'string') { if (!validatorsMap[schema.$ref]) { validatorsMap[schema.$ref] = ajv.getSchema(schema.$ref); } return validatorsMap[schema.$ref]; } if (!SCHEMA_VALIDATORS_OPTIONS.lazy) { log('debug', `⚠️ - Prefer using $ref to OpenAPI schemas components to build more efficiently!`); log('debug', JSON.stringify(schema)); } if (SCHEMA_VALIDATORS_OPTIONS.dedupe) { const key = createHash(Buffer.from(JSON.stringify(schema)), SCHEMA_VALIDATORS_OPTIONS.hashLength); if (!validatorsMap[key]) { validatorsMap[key] = ajv.compile(schema); } return validatorsMap[key]; } return ajv.compile(schema); }; } export default location(autoService(initSchemaValidators), import.meta.url); export async function buildSchemaValidatorsMap({ DEBUG_NODE_ENVS, SCHEMA_VALIDATORS_OPTIONS = DEFAULT_SCHEMA_VALIDATORS_OPTIONS, API, ENV, log, }) { if (SCHEMA_VALIDATORS_OPTIONS.lazy) { log('warning', `⚠️ - Using lazy compilation is not recommended when building schema validators.`); } const ajv = new Ajv2020({ verbose: DEBUG_NODE_ENVS.includes(ENV.NODE_ENV), strict: true, logger: { log: (...args) => log('debug', ...args), warn: (...args) => log('warning', ...args), error: (...args) => log('error', ...args), }, // Using common JS since bugs with ESM // See: https://github.com/ajv-validator/ajv/issues/2598 code: { source: true, esm: false }, }); addAJVFormats.default(ajv); const schemas = collectAPISchemas(API); const schemaMapper = {}; schemas.push(...(await extractAPISecurityParametersSchemas({ API: API, })).map((schema) => ({ schema, location: { components: 'headers', headerName: '', }, }))); for (const schema of schemas) { if ('components' in schema.location && schema.location.components === 'schemas') { const $ref = '#/components/schemas/' + schema.location.schemaName; schemaMapper['__schema_' + schema.location.schemaName] = $ref; ajv.addSchema(schema.schema, $ref); } } for (const schema of schemas) { if ('components' in schema.location && schema.location.components === 'schemas') { continue; } const key = '__hash_' + createHash(Buffer.from(JSON.stringify(schema.schema)), SCHEMA_VALIDATORS_OPTIONS.hashLength); const $ref = '#/components/hash/' + key; if (ajv.getSchema($ref)) { continue; } schemaMapper[key] = $ref; ajv.addSchema(schema.schema, $ref); } return standaloneCode(ajv, schemaMapper); } //# sourceMappingURL=schemaValidators.js.map