From 6bb47b3ccb34819c167a698de12ba23162e8136c Mon Sep 17 00:00:00 2001 From: mferrera Date: Thu, 19 Dec 2024 12:43:46 +0100 Subject: [PATCH] CI: Validate schema with AJV --- .github/workflows/schemas-up-to-date.yml | 5 ++ .gitignore | 5 ++ tools/schema-validate-ajv.mjs | 95 ++++++++++++++++++++++++ 3 files changed, 105 insertions(+) create mode 100755 tools/schema-validate-ajv.mjs diff --git a/.github/workflows/schemas-up-to-date.yml b/.github/workflows/schemas-up-to-date.yml index 85e8a6aea..e5d02c11d 100644 --- a/.github/workflows/schemas-up-to-date.yml +++ b/.github/workflows/schemas-up-to-date.yml @@ -26,3 +26,8 @@ jobs: run: | ./tools/update_schema git diff --exit-code + + - name: Ensure schema validates with AJV + run: | + npm install ajv ajv-formats + ./tools/schema-validate-ajv.mjs ./schema/ diff --git a/.gitignore b/.gitignore index a4d2673c2..6c43ee20e 100644 --- a/.gitignore +++ b/.gitignore @@ -106,3 +106,8 @@ examples/s/d/nn/xcase/iter-0/ # Apple macOS .DS_Store + +# npm/node +node_modules/ +package-lock.json +package.json diff --git a/tools/schema-validate-ajv.mjs b/tools/schema-validate-ajv.mjs new file mode 100755 index 000000000..0eb684392 --- /dev/null +++ b/tools/schema-validate-ajv.mjs @@ -0,0 +1,95 @@ +#!/usr/bin/env node + +import Ajv2020 from "ajv/dist/2020.js" +import addFormats from "ajv-formats"; +import fs from "fs/promises"; +import path from "path"; +import { fileURLToPath } from "url"; + +const ajv = new Ajv2020({ strict: false, discriminator: true }); +addFormats(ajv); + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +const GREEN = "\x1b[32m"; +const RED = "\x1b[31m"; +const YELLOW = "\x1b[93m"; +const NC = "\x1b[0m"; +const BOLD = "\x1b[1m"; +const SUCCESS = `[${BOLD}${GREEN}✔${NC}]`; +const FAILURE = `[${BOLD}${RED}✖${NC}]`; +const INFO = `[${BOLD}${YELLOW}+${NC}]`; + +async function loadJson(filePath) { + try { + const fullPath = path.resolve(filePath); + const fileContent = await fs.readFile(fullPath, "utf-8"); + return JSON.parse(fileContent); + } catch (error) { + console.error(`${FAILURE} Error reading file at ${filePath}:`, error.message); + process.exit(1); + } +} + +async function validateSchema(schemaPath) { + try { + const schema = await loadJson(schemaPath); + const validate = ajv.compile(schema); + console.log(`${SUCCESS} Schema is valid: ${schemaPath}`); + return validate; + } catch (error) { + console.error(`${FAILURE} Schema is invalid: ${schemaPath}`); + console.error(` ${INFO} Reason: ${error.message}`); + return false; + } +} + +async function findJsonFiles(dir) { + let results = []; + const entries = await fs.readdir(dir, { withFileTypes: true }); + + for (const entry of entries) { + const fullPath = path.join(dir, entry.name); + if (entry.isDirectory()) { + results = results.concat(await findJsonFiles(fullPath)); + } else if (entry.isFile() && entry.name.endsWith(".json")) { + results.push(fullPath); + } + } + + return results; +} + +async function validateSchemasInDirectory(dirPath) { + const schemaFiles = await findJsonFiles(dirPath); + + if (schemaFiles.length === 0) { + console.log(`${INFO} No JSON schema files found in directory: ${dirPath}`); + return; + } + + let shouldFail = false; + console.log(`${INFO} Found ${schemaFiles.length} JSON schema file(s) in directory: ${dirPath}`); + for (const schemaPath of schemaFiles) { + const isValid = await validateSchema(schemaPath); + if (!isValid) { + shouldFail = true; + } + } + if (shouldFail) { + process.exit(1); + } +} + +const directory = process.argv[2]; + +if (!directory) { + console.error("Usage: validate-schema-ajv.mjs "); + process.exit(1); +} + +validateSchemasInDirectory(directory).catch((err) => { + console.error(`${FAILURE} Unknown error: ${err.message}`); + process.exit(1); +});