UNPKG

typescript-scaffolder

Version:

![npm version](https://img.shields.io/npm/v/typescript-scaffolder) ![coverage](https://img.shields.io/badge/coverage-97.38%25-green)

198 lines (197 loc) 7.33 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.assertRequiredFields = assertRequiredFields; exports.assertStructure = assertStructure; exports.assertNoDuplicateKeys = assertNoDuplicateKeys; exports.assertEnumValue = assertEnumValue; exports.assertInRange = assertInRange; exports.assertSequences = assertSequences; exports.assertSequence = assertSequence; exports.assertSequenceStep = assertSequenceStep; const logger_1 = require("./logger"); /** * Checks an object to see if all required fields are present * @param obj * @param required */ function assertRequiredFields(obj, required) { const funcName = 'assertRequiredFields'; logger_1.Logger.debug(funcName, 'Checking required fields', required); const missing = required.filter((key) => !(key in obj)); if (missing.length > 0) { const err = `Missing required fields: ${missing.join(', ')}`; logger_1.Logger.error(funcName, err); } logger_1.Logger.debug(funcName, 'All required fields are present'); } /** * Checks an object to see if the structure is correct to what is expected * @param obj * @param structure */ function assertStructure(obj, structure) { const funcName = 'assertStructure'; logger_1.Logger.debug(funcName, 'Asserting structure', structure); for (const [key, expectedType] of Object.entries(structure)) { if (typeof obj[key] !== expectedType) { const err = `Field '${key}' should be of type '${expectedType}' but got '${typeof obj[key]}'`; logger_1.Logger.warn(funcName, err); } } logger_1.Logger.debug(funcName, 'Structure successfully asserted'); } /** * Checks to see if there are any duplicate keys within the JSON * @param jsonString */ function assertNoDuplicateKeys(jsonString, filename) { const funcName = 'assertNoDuplicateKeys'; logger_1.Logger.debug(funcName, 'Asserting no duplicate keys', jsonString); const seenKeys = new Set(); const regex = /"([^"\\]*(?:\\.[^"\\]*)*)"\s*:/g; let match; while ((match = regex.exec(jsonString))) { const key = match[1]; if (seenKeys.has(key)) { const err = `Duplicate key detected: ${key} in ${filename}`; logger_1.Logger.warn(funcName, err); } seenKeys.add(key); } logger_1.Logger.debug(funcName, 'Asserted no duplicate keys'); } /** * Checks to see if all values within an enum are valid * @param field * @param value * @param allowed */ function assertEnumValue(field, value, allowed) { const funcName = 'assertEnumValue'; logger_1.Logger.debug(funcName, 'Asserting enum value', field, value); if (!allowed.includes(value)) { const err = `Field '${field}' must be one of ${JSON.stringify(allowed)}, got '${value}'`; logger_1.Logger.warn(funcName, err); } logger_1.Logger.debug(funcName, 'Asserted enum values'); } /** * Checks to see whether the value declared in a spec is within the min and max range * @param field * @param value * @param min * @param max */ function assertInRange(field, value, min, max) { const funcName = 'assertInRange'; logger_1.Logger.debug(funcName, 'Asserting in range', field, value, min, max); if (typeof value !== 'number') { const err = `Field '${field}' must be a number, got '${typeof value}'`; logger_1.Logger.warn(funcName, err); } if (value < min || value > max) { const err = `Field '${field}' must be between ${min} and ${max}, got ${value}`; logger_1.Logger.warn(funcName, err); } logger_1.Logger.debug(funcName, 'Asserted fields are in range'); } /** * Validates a top-level sequences array, if present. * @param config The full parsed configuration object */ function assertSequences(config) { const funcName = 'assertSequences'; logger_1.Logger.debug(funcName, 'Validating sequences block'); const sequences = config.sequences; if (sequences === undefined || sequences === null) { logger_1.Logger.error(funcName, 'No sequences defined'); return; } if (!Array.isArray(sequences)) { logger_1.Logger.error(funcName, '`sequences` must be an array'); return; } for (const seq of sequences) { assertSequence(seq); } logger_1.Logger.debug(funcName, 'Sequences block validated'); } /** * Validates a single sequence object. * Ensures required fields and step-array structure before recursing. * @param seq The sequence object to validate */ function assertSequence(seq) { const funcName = 'assertSequence'; logger_1.Logger.debug(funcName, 'Validating sequence', seq.name); if (seq.name === undefined) { logger_1.Logger.error(funcName, "Missing required field 'name'"); return; } if (seq.steps === undefined) { logger_1.Logger.error(funcName, "Missing required field 'steps'"); return; } if (!Array.isArray(seq.steps)) { logger_1.Logger.error(funcName, `\`steps\` must be an array in sequence ${seq.name}`); return; } for (const step of seq.steps) { assertSequenceStep(step); } logger_1.Logger.debug(funcName, 'Sequence validated', seq.name); } /** Validate a single step based on its type */ function assertSequenceStep(step) { const funcName = 'assertSequenceStep'; logger_1.Logger.debug(funcName, 'Validating step', step.id); assertRequiredFields(step, ['id', 'type']); switch (step.type) { case 'fetchList': assertFetchListStep(step); break; case 'action': assertActionStep(step); break; case 'loop': assertLoopStep(step); break; default: logger_1.Logger.error(funcName, `Unknown step type '${step.type}' in step ${step.id}`); } } /** Validate a fetchList step */ function assertFetchListStep(step) { const funcName = 'assertFetchListStep'; logger_1.Logger.debug(funcName, 'Validating fetchList step', step.id); assertRequiredFields(step, ['endpoint', 'extract']); assertStructure(step, { endpoint: 'string', extract: 'object' }); if (step.extract) { assertStructure(step.extract, { as: 'string', field: 'string' }); } logger_1.Logger.debug(funcName, 'fetchList step validated', step.id); } /** Validate an action step */ function assertActionStep(step) { const funcName = 'assertActionStep'; logger_1.Logger.debug(funcName, 'Validating action step', step.id); assertRequiredFields(step, ['endpoint']); assertStructure(step, { endpoint: 'string' }); if (step.extract) { assertStructure(step.extract, { as: 'string', field: 'string' }); } logger_1.Logger.debug(funcName, 'action step validated', step.id); } /** Validate a loop step */ function assertLoopStep(step) { const funcName = 'assertLoopStep'; logger_1.Logger.debug(funcName, 'Validating loop step', step.id); assertRequiredFields(step, ['over', 'itemName', 'steps']); assertStructure(step, { over: 'string', itemName: 'string', steps: 'object' }); if (Array.isArray(step.steps)) { for (const nested of step.steps) { assertSequenceStep(nested); } } logger_1.Logger.debug(funcName, 'loop step validated', step.id); }