@apistudio/apim-cli
Version:
CLI for API Management Products
262 lines (261 loc) • 12 kB
JavaScript
import JSZip from 'jszip';
import yaml from 'js-yaml';
import { addErrorToResponse, isValidAsset, processRef, updateMapWithMetadata, validateMinAssets, validateYamlFiles, checkFileExtension } from '../utils.js';
import { LogWrapper } from '../service/log-wrapper.js';
import { AppConstants } from '../constants/app.constants.js';
import path from 'path';
import { BuildProjectAssets } from '../build-project-assets.js';
export class ProjectAssetValidator {
isYamlFileForFolder(entry, folderName) {
return (!entry.dir &&
entry.name.startsWith(folderName + path.sep) && checkFileExtension(entry.name));
}
async loadZipFromBuffer(fileBuffer) {
LogWrapper.logInfo('0003', 'Loading ZIP from buffer.');
const zip = new JSZip();
return zip.loadAsync(fileBuffer);
}
async createProjectAssetReferenceMap(buffer, folderName, allFolderNames) {
LogWrapper.logInfo('0351', 'asset', `${folderName}`);
const zipContent = await this.loadZipFromBuffer(buffer);
const refMap = new Map();
try {
const obj = new BuildProjectAssets();
const versionMap = await obj.createVersionProcessingMap(buffer);
await this.processYamlFiles(zipContent, folderName, refMap, versionMap);
await this.processYamlFiles(zipContent, 'dependencies', refMap, versionMap, false);
// if unresolved refs are identified after checking in the project and dependencies folders then check in other folders
const hasUnresolvedRefs = Array.from(refMap.values()).some(value => !value);
if (hasUnresolvedRefs) {
for (const otherFolderName of allFolderNames) {
if (otherFolderName !== folderName && otherFolderName !== 'dependencies') {
await this.processYamlFiles(zipContent, otherFolderName, refMap, versionMap, false);
}
}
}
LogWrapper.logInfo('0003', 'Successfully processed YAML files.');
}
catch (err) {
LogWrapper.logError('0013', 'processing ZIP', `${err}`);
}
return refMap;
}
async processYamlFiles(zipContent, folderName, refMap, versionMap, processDependencies = true) {
LogWrapper.logInfo('0352', folderName);
await Promise.all(Object.keys(zipContent.files).map(async (fileName) => {
const entry = zipContent.files[fileName];
if (this.isYamlFileForFolder(entry, folderName)) {
await this.processYamlFile(entry, refMap, processDependencies, versionMap);
}
}));
}
async processYamlFile(entry, refMap, processDependencies, versionMap) {
LogWrapper.logInfo('0353', entry.name);
const content = await entry.async('string');
try {
const yamlContents = yaml.loadAll(content);
for (const yamlContent of yamlContents) {
if (isValidAsset(yamlContent)) {
this.updateReferenceMap(yamlContent, refMap, processDependencies, versionMap);
}
}
LogWrapper.logInfo('0354', AppConstants.YAMLContent, entry.name);
}
catch (err) {
LogWrapper.logError('0013', `parsing YAML in file ${entry.name}`, `${err}`);
}
}
updateReferenceMap(yamlContent, refMap, processDependencies, versionMap) {
if (processDependencies) {
this.extractKey(yamlContent, refMap, '$ref', versionMap, processRef);
}
updateMapWithMetadata(yamlContent, refMap);
}
extractKey(yamlContent, refMap, keyToExtract, versionMap, transformValue) {
const extract = (obj) => {
for (const key in obj) {
const value = obj[key];
if (key === keyToExtract && typeof value === 'string') {
const transformedValue = transformValue ? transformValue(value) : value;
if (versionMap.has(value)) {
if (!refMap.has(value)) {
refMap.set(value, true);
}
}
else {
if (!refMap.has(transformedValue)) {
refMap.set(transformedValue, false);
}
}
}
else if (typeof value === 'object' && value !== null) {
extract(value);
}
}
};
const specOb = JSON.stringify(yamlContent.spec);
extract(yaml.load(specOb));
}
async createProjectPathReferenceMap(buffer, folderName) {
LogWrapper.logInfo('0351', 'path', folderName);
const zipContent = await this.loadZipFromBuffer(buffer);
const refMap = new Map();
try {
const obj = new BuildProjectAssets();
const versionMap = await obj.createVersionProcessingMap(buffer);
for (const fileName in zipContent.files) {
const entry = zipContent.files[fileName];
if (this.isYamlFileForFolder(entry, folderName)) {
const content = await entry.async('string');
try {
const yamlContents = yaml.loadAll(content);
for (const yamlContent of yamlContents) {
if (isValidAsset(yamlContent)) {
this.extractKey(yamlContent, refMap, '$path', versionMap, path.normalize);
}
}
LogWrapper.logInfo('0354', 'path extraction', fileName);
}
catch (err) {
LogWrapper.logError('0013', `parsing YAML in file ${fileName}`, `${err}`);
}
}
}
}
catch (err) {
LogWrapper.logError('0013', 'loading ZIP', `${err}`);
}
return refMap;
}
async validateApiSpecVaraible(buffer, folderName) {
LogWrapper.logInfo('0355', 'API Spec variable', folderName);
const zipContent = await this.loadZipFromBuffer(buffer);
for (const fileName in zipContent.files) {
const entry = zipContent.files[fileName];
if (!entry.dir &&
fileName.startsWith(folderName + path.sep) && checkFileExtension(fileName)) {
const content = await entry.async('string');
const isValid = await this.checkYamlContent(content, fileName);
if (!isValid) {
return false;
}
}
}
return true;
}
async checkYamlContent(content, fileName) {
LogWrapper.logInfo('0357', AppConstants.YAMLContent, fileName);
try {
const yamlContents = yaml.loadAll(content);
for (const yamlContent of yamlContents) {
if (isValidAsset(yamlContent) && yamlContent.kind?.toLowerCase() === 'api') {
const specObj = JSON.stringify(yamlContent.spec);
const apiSpec = yaml.load(specObj);
if (this.isInvalidApiSpec(apiSpec)) {
addErrorToResponse(AppConstants.VALIDATION_ERROR_CODE, fileName, `Validation failed for api-spec field in file ${fileName}`);
return false;
}
}
}
LogWrapper.logInfo('0358', AppConstants.YAMLContent, fileName);
}
catch (err) {
LogWrapper.logError('0013', `parsing YAML in file ${fileName}`, `${err}`);
return false;
}
return true;
}
isInvalidApiSpec(apiSpec) {
const apiSpecField = AppConstants.apiSpec;
const apiSpecPathLength = apiSpec[apiSpecField]?.$path?.length ?? 0;
return !apiSpec ||
!apiSpec[apiSpecField] ||
!apiSpec[apiSpecField].$path ||
(apiSpecPathLength <= 0);
}
async validateProjectAssetReference(buffer, folderName, allFolderNames) {
LogWrapper.logInfo('0355', 'project asset references', folderName);
let refMap = new Map();
try {
refMap = await this.createProjectAssetReferenceMap(buffer, folderName, allFolderNames);
const allRefsValid = Array.from(refMap.entries()).every(([key, value]) => {
if (!value) {
addErrorToResponse(AppConstants.VALIDATION_ERROR_CODE, key, `Validation failed for Reference ${key}`);
}
return value;
});
if (!allRefsValid) {
LogWrapper.logError('0255', 'Some references are not valid.');
return { isValid: false, refMap };
}
return { isValid: true, refMap };
}
catch (err) {
LogWrapper.logError('0013', 'validating asset', `${err}`);
return { isValid: false, refMap };
}
}
async validateProjectPathReference(buffer, folderName, filePathsInFolder) {
LogWrapper.logInfo('0355', 'project path references', folderName);
try {
const refMap = await this.createProjectPathReferenceMap(buffer, folderName);
refMap.forEach((_, key) => {
if (filePathsInFolder.has(path.normalize(`${folderName}/${key}`))) {
refMap.set(key, true);
}
});
const allRefsValid = Array.from(refMap.entries()).every(([key, value]) => {
if (!value) {
console.log(`Validation failed for path ${key} in ${folderName}`);
LogWrapper.logError('0003', `Validation failed for path ${key} in ${folderName}`);
addErrorToResponse(AppConstants.VALIDATION_ERROR_CODE, key, `Validation failed for path ${key} in ${folderName}`);
}
return value;
});
if (!allRefsValid) {
LogWrapper.logError('0255', 'Some references are not valid.');
return false;
}
return true;
}
catch (err) {
LogWrapper.logError('0013', 'validating asset', `${err}`);
return false;
}
}
async validateDeploymentAsset(buffer) {
try {
const zipContent = await this.loadZipFromBuffer(buffer);
LogWrapper.logDebug('0308', `${Object.keys(zipContent.files).length}`);
for (const fileName in zipContent.files) {
if (checkFileExtension(fileName)) {
const entry = zipContent.files[fileName];
const content = await entry.async('string');
const yamlContents = yaml.loadAll(content);
for (const yamlContent of yamlContents) {
if (isValidAsset(yamlContent)) {
return true;
}
}
}
}
}
catch (err) {
addErrorToResponse(AppConstants.VALIDATION_ERROR_CODE, 'ZIP_FILE', `Error loading zip with minimum assets: ${err}`);
}
addErrorToResponse(AppConstants.VALIDATION_ERROR_CODE, 'ZIP_FILE', 'Error loading zip with minimum assets required for deploymnet');
return false;
}
async validateProjectHasMinimumAssets(buffer) {
LogWrapper.logInfo('0003', 'Validating project has minimum assets.');
return await validateMinAssets(buffer) && await this.validateDeploymentAsset(buffer);
}
async validateProjectApiSpecVariable(buffer, folderName) {
LogWrapper.logInfo('0355', 'project API spec variables', folderName);
return this.validateApiSpecVaraible(buffer, folderName);
}
async validateYaml(buffer) {
LogWrapper.logInfo('0003', 'Validating YAML files.');
return validateYamlFiles(buffer);
}
}