UNPKG

@apistudio/apim-cli

Version:

CLI for API Management Products

267 lines (266 loc) 9.91 kB
/** * Copyright Super iPaaS Integration LLC, an IBM Company 2024 */ import yaml from 'js-yaml'; import JSZip from 'jszip'; import { UpperCaseKinds } from './model/interface.js'; import { AssetValidator } from './service/validation-service.js'; import { AppConstants } from './constants/app.constants.js'; import path from 'path'; export const validateYamlFiles = async (buffer) => { const zip = new JSZip(); let allValid = true; try { const zipContent = await zip.loadAsync(buffer); //logDebug('0308', `${Object.keys(zipContent.files).length}`); for (const fileName in zipContent.files) { const entry = zipContent.files[fileName]; if (!entry.dir && (fileName.endsWith('.yaml') || fileName.endsWith('.yml'))) { const content = await entry.async('string'); //logInfo('0353', fileName); try { const yamlContents = yaml.loadAll(content); if (!validatYamlContent(yamlContents, fileName)) { allValid = false; } } catch (err) { //logError('0013', 'while parsing YAML', `${err}`); addErrorToResponse(AppConstants.VALIDATION_ERROR_CODE, AppConstants.YAML_FILE, `Invalid YAML in file ${fileName}: ${err}`); allValid = false; } } } } catch (err) { //logError('0013', 'while loading ZIP', `${err}`); addErrorToResponse(AppConstants.VALIDATION_ERROR_CODE, 'ZIP_FILE', `Error loading ZIP: ${err}`); allValid = false; } return allValid; }; export const createAssetReferenceMap = async (buffer) => { const zip = new JSZip(); const refMap = new Map(); try { const zipContent = await zip.loadAsync(buffer); //logDebug('0308', `${Object.keys(zipContent.files).length}`); for (const fileName in zipContent.files) { const entry = zipContent.files[fileName]; if (!entry.dir && (fileName.endsWith('.yaml') || fileName.endsWith('.yml')) && !fileName.includes('resources')) { const content = await entry.async('string'); //logInfo('0353', fileName); try { const yamlContents = yaml.loadAll(content); for (const yamlContent of yamlContents) { extractRefs(yamlContent, refMap); updateMapWithMetadata(yamlContent, refMap); } } catch (err) { //logError('0013', 'parsing YAML', `${err}`); } } } } catch (err) { //logError('0013', 'loading ZIP', `${err}`); } return refMap; }; export const extractRefs = (yamlContent, refMap) => { const extractRef = (obj) => { for (const key in obj) { const value = obj[key]; if (key === '$ref' && typeof value === 'string') { if (!refMap.has(value)) { refMap.set(value, false); //logDebug('0309', value); } } else if (typeof value === 'object' && value !== null) { extractRef(value); } } }; const specOb = JSON.stringify(yamlContent.spec); extractRef(yaml.load(specOb)); }; export const updateMapWithMetadata = (yamlContent, refMap) => { const metadata = yamlContent['metadata']; const keyParts = []; if (metadata.namespace) { keyParts.push(metadata.namespace); } if (metadata.name) { keyParts.push(metadata.name); } const version = convertNumberToString(metadata.version); if (metadata.version) { keyParts.push(version); } const key = keyParts.join(':'); refMap.set(key, true); //logDebug('0310', 'Metadata', key); }; export const convertNumberToString = (data) => { if (typeof data === 'string') { return data.trim(); } else if (Math.abs(data - Math.floor(data)) < 1e-7) { return data.toFixed(1); } else { return data.toString(); } }; export const isValidAsset = (yamlContent) => { return !!(yamlContent?.kind && yamlContent.kind.toLowerCase() !== AppConstants.TEST && yamlContent.kind.toLowerCase() !== AppConstants.ASSERTION && yamlContent.kind.toLowerCase() !== AppConstants.ENVIRONMENT && yamlContent?.metadata?.name && yamlContent?.metadata?.version && yamlContent?.spec && UpperCaseKinds.includes(yamlContent.kind.toUpperCase())); }; const errorsArray = []; export const addErrorToResponse = (errorCode, field, description) => { errorsArray.push({ code: errorCode, field: field, description: description }); //logDebug('0363', description); }; export const constructErrorResponse = () => { const tempErrorsArray = [...errorsArray]; errorsArray.length = 0; return { respCode: 400, message: 'Invalid Assets or Reference in the Zip', Endpoints: [], errors: tempErrorsArray }; }; export const createPathReferenceMap = async (buffer) => { const zip = new JSZip(); const refMap = new Map(); try { const zipContent = await zip.loadAsync(buffer); //logDebug('0308', `${Object.keys(zipContent.files).length}`); for (const fileName in zipContent.files) { const entry = zipContent.files[fileName]; if (!entry.dir && (fileName.endsWith('.yaml') || fileName.endsWith('.yml')) && !fileName.includes('resources')) { const content = await entry.async('string'); //logInfo('0353', fileName); try { const yamlContents = yaml.loadAll(content); for (const yamlContent of yamlContents) { extractPath(yamlContent, refMap); } } catch (err) { //logError('0013', 'parsing YAML', `${err}`); } } } } catch (err) { //logError('0013', 'loading ZIP', `${err}`); } return refMap; }; export const extractPath = (yamlContent, refMap) => { const buildPathRefMap = (obj) => { for (const key in obj) { const value = obj[key]; if (key === '$path' && typeof value === 'string') { if (!refMap.has(value)) { refMap.set(path.normalize(value), false); //logDebug('0309', value); } } else if (typeof value === 'object' && value !== null) { buildPathRefMap(value); } } }; const specOb = JSON.stringify(yamlContent.spec); buildPathRefMap(yaml.load(specOb)); }; export const updatePathRefMap = async (buffer, refMap) => { const zip = await JSZip.loadAsync(buffer); //logDebug('0003', 'Updating path references from resources directory.'); zip.forEach((relativePath) => { if (relativePath.startsWith('resources/')) { const modifiedFileName = relativePath.replace('resources/', ''); if (refMap.has(modifiedFileName)) { refMap.set(modifiedFileName, true); //logDebug('0310', 'Path', modifiedFileName); } } }); }; export const validateMinAssets = async (buffer) => { const zip = new JSZip(); let valid = false; try { const zipContent = await zip.loadAsync(buffer); //logDebug('0308', `${Object.keys(zipContent.files).length}`); for (const fileName in zipContent.files) { if (fileName.endsWith('.yaml') || fileName.endsWith('.yml')) { valid = true; break; } } } catch (err) { //logError('0013', 'loading zip with minimum assets', `${err}`); addErrorToResponse(AppConstants.VALIDATION_ERROR_CODE, 'ZIP_FILE', `Error loading zip with minimum assets: ${err}`); valid = false; } return valid; }; const validatYamlContent = (yamlContents, fileName) => { let allValid = true; const assetValidator = new AssetValidator(); for (const yamlContent of yamlContents) { if (yamlContent?.kind != null && !assetValidator.validateAssets(yamlContent)) { //logError('0361', fileName); addErrorToResponse(AppConstants.VALIDATION_ERROR_CODE, AppConstants.YAML_FILE, `Invalid YAML in file ${fileName}`); allValid = false; } } return allValid; }; export const updateRefs = (yamlContent, versionMap) => { const updateRef = (obj, verionMap) => { for (const key in obj) { const value = obj[key]; if (key === '$ref' && typeof value === 'string') { if (!verionMap.get(value)) { obj[key] = processRef(value); } else { obj[key] = value; } } else if (typeof value === 'object' && value !== null) { updateRef(value, verionMap); } } }; //@ts-expect-error: This error is expected because of proto object is of type any or undefined updateRef(yamlContent.spec, versionMap); return yamlContent; }; export const processRef = (value) => { const parts = value.split(':'); const numberValue = parseFloat(parts[parts.length - 1]); if (!isNaN(numberValue)) { parts[parts.length - 1] = convertNumberToString(numberValue); } return parts.join(':'); }; export function checkFileExtension(name) { return name.endsWith('.yml') || name.endsWith('.yaml'); } export function isRelativePath(file) { return file.startsWith('./') || file.startsWith('../'); }