UNPKG

stac-node-validator

Version:
242 lines (219 loc) 6.64 kB
const klaw = require('klaw'); const fs = require('fs-extra'); const path = require('path'); const { makeAjvErrorMessage, isHttpUrl, SUPPORTED_PROTOCOLS } = require('./utils'); const SCHEMA_CHOICE = ['anyOf', 'oneOf']; function abort(message) { console.error(message); process.exit(1); } function printConfig(config) { console.group('Config'); console.dir(config); console.groupEnd(); } function printSummary(summary) { console.group(`Summary (${summary.total})`); console.log('Passed: ' + summary.valid); console.log('Invalid: ' + summary.invalid); if (summary.malformed !== null) { console.log('Malformed: ' + summary.malformed); } console.log('Skipped: ' + summary.skipped); console.groupEnd(); } function printLint(lint, config) { const what = []; config.lint && what.push('Linting'); config.format && what.push('Formatting'); const title = what.join(' and '); if (!lint) { if (config.lint || config.format) { console.group(title); console.log('Not supported for remote files'); console.groupEnd(); } return; } if (config.verbose) { console.group(title); if (lint.valid) { console.log('File is well-formed'); } else { if (lint.fixed) { console.log('File was malformed -> fixed the issue'); } else { console.log('File is malformed -> use `--format` to fix the issue'); } } if (lint.error) { console.log(lint.error); } if (lint.diff) { console.groupCollapsed('File Diff'); console.log(lint.diff); console.groupEnd(); } console.groupEnd(); } else if (!lint.valid && !lint.fixed) { console.group(title); console.log('File is malformed -> use `--format` to fix the issue'); if (lint.error) { console.log(lint.error); } console.groupEnd(); } } function printReport(report, config) { if (report.valid && !config.verbose) { return; } console.group(report.id || 'Report'); if (config.verbose && report.version) { console.log(`STAC Version: ${report.version}`); } if (report.messages) { report.messages.forEach((str) => console.log(str)); } if (!report.apiList) { printLint(report.lint, config); } if (!report.valid || config.verbose) { printAjvValidationResult(report.results.core, report.type, report.valid, config); if (report.type) { const count = Object.keys(report.results.extensions).length; if (count > 0) { console.group('Extensions'); Object.entries(report.results.extensions).forEach(([ext, result]) => printAjvValidationResult(result, ext, report.valid, config), ); console.groupEnd(); } else { console.log('Extensions: None'); } } if (config.custom && (report.results.custom.length > 0 || report.children.length === 0)) { printAjvValidationResult(report.results.custom, 'Custom', report.valid, config); } } report.children.forEach((child) => printReport(child, config)); console.groupEnd(); } function printAjvValidationResult(result, category, reportValid, config) { if (!category) { return; } if (!config.verbose && isHttpUrl(category)) { const match = category.match(/^https?:\/\/stac-extensions\.github\.io\/([^/]+)\/v?([^/]+)(?:\/([^/.]+))?\/schema/); if (match) { let title = match[1]; if (match[3]) { title += ' - ' + formatKey(match[3]); } category = `${title} (${match[2]})`; } } if (result.length > 0) { console.group(category); if (config.verbose) { console.dir(result); } else { result .filter((error) => result.length === 1 || !SCHEMA_CHOICE.includes(error.keyword)) // Remove messages that are usually not so important (anyOf/oneOf) .sort((a, b) => { // Sort so that anyOf/oneOf related messages come last, these are usually not so important let aa = isSchemaChoice(a.schemaPath); let bb = isSchemaChoice(b.schemaPath); if (aa && bb) { return 0; } else if (aa) { return 1; } else if (bb) { return -1; } else { return 0; } }) .map((error) => makeAjvErrorMessage(error)) // Convert to string .filter((value, i, array) => array.indexOf(value) === i) // Remove duplicates .forEach((msg, i) => console.log(`${i + 1}. ${msg}`)); // Print it as list } console.groupEnd(); } else if (!reportValid || config.verbose) { console.log(`${category}: passed`); } } function isSchemaChoice(schemaPath) { return typeof schemaPath === 'string' && schemaPath.match(/\/(one|any)Of\/\d+\//); } async function resolveFiles(files, depth = -1) { const result = { files: [], error: {}, }; const extensions = ['.geojson', '.json']; const klawOptions = { depthLimit: depth, }; for (const file of files) { const url = URL.parse(file); // url.protocol.length > 2 check that it's not a Windows path, e.g. c: as in c://foo/bar if (url && url.protocol && url.protocol.length > 2 && url.protocol !== 'file:') { if (SUPPORTED_PROTOCOLS.includes(url.protocol)) { result.files.push(file); continue; } else { result.error[file] = new Error(`Protocol "${url.protocol}" is not supported.`); } } else { try { const stat = await fs.lstat(file); if (stat.isDirectory()) { // Special handling for reading directories for await (const child of klaw(file, klawOptions)) { const ext = path.extname(child.path).toLowerCase(); if (extensions.includes(ext)) { result.files.push(child.path); } } } else { result.files.push(file); } } catch (error) { result.error[file] = error; } } } return result; } function strArrayToObject(list, sep = '=') { let map = {}; for (let str of list) { let [key, value] = str.split(sep, 2); map[key] = value; } return map; } // This is taken from stac-fields package function formatKey(key, prefix = false) { if (key.includes('/')) { // Slashes are strong indicators for URIs or media types, don't format return key; } if (prefix === false) { key = key.replace(/^[\w-]+:/i, ''); } return key .split(/[:_\-\s]/g) .map((part) => part.charAt(0).toUpperCase() + part.slice(1)) .join(' '); } module.exports = { abort, formatKey, printConfig, printReport, printSummary, resolveFiles, strArrayToObject, };