From 099c14a56953cb2f28294f66050af1b7b083b045 Mon Sep 17 00:00:00 2001 From: Dan Delany Date: Mon, 10 Feb 2025 14:02:51 -0800 Subject: [PATCH] Improve schema for Time object to generate better TS types (#35) * improve schema for Time object to generate better TS types * improve tests to report validation errors * fix small issue with test script * remove top-level additionalProperties: false from time type to avoid issues with oneOf * add test for comparing version strings in schema/package files * bump version in schema.json --- package-lock.json | 4 ++-- package.json | 2 +- schema.json | 45 +++++++++++++++++++++-------------------- test/test.js | 51 ++++++++++++++++++++++++++++++++++++++++------- 4 files changed, 70 insertions(+), 32 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2baf05a..7ca24e1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@nasa-jpl/seq-json-schema", - "version": "1.3.0", + "version": "1.3.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@nasa-jpl/seq-json-schema", - "version": "1.2.0", + "version": "1.3.1", "license": "MIT", "devDependencies": { "ajv": "^8.12.0", diff --git a/package.json b/package.json index d6cc275..e8e2b1a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@nasa-jpl/seq-json-schema", - "version": "1.3.0", + "version": "1.3.1", "license": "MIT", "type": "module", "repository": { diff --git a/schema.json b/schema.json index b60a133..547b89c 100644 --- a/schema.json +++ b/schema.json @@ -1,5 +1,5 @@ { - "$id": "https://github.com/NASA-AMMOS/seq-json-schema/tree/v1.3.0", + "$id": "https://github.com/NASA-AMMOS/seq-json-schema/tree/v1.3.1", "$schema": "https://json-schema.org/draft/2020-12/schema", "$defs": { "immediate_activate": { @@ -475,32 +475,33 @@ "type": "object" }, "time": { - "additionalProperties": false, "description": "Time object", - "properties": { - "tag": { - "description": "Relative or absolute time. Required for ABSOLUTE, BLOCK_RELATIVE, COMMAND_RELATIVE, and EPOCH_RELATIVE time types but not COMMAND_COMPLETE.", - "type": "string" + "oneOf": [ + { + "additionalProperties": false, + "properties": { + "type": { + "description": "Allowed time types without a tag: COMMAND_COMPLETE", + "enum": ["COMMAND_COMPLETE"], + "type": "string" + } + }, + "required": ["type"] }, - "type": { - "description": "Allowed time types: ABSOLUTE, BLOCK_RELATIVE, COMMAND_RELATIVE, EPOCH_RELATIVE, or COMMAND_COMPLETE.", - "enum": ["ABSOLUTE", "BLOCK_RELATIVE", "COMMAND_RELATIVE", "EPOCH_RELATIVE", "COMMAND_COMPLETE"], - "type": "string" - } - }, - "required": ["type"], - "allOf": [ { - "if": { - "properties": { - "type": { - "enum": ["ABSOLUTE", "BLOCK_RELATIVE", "COMMAND_RELATIVE", "EPOCH_RELATIVE"] - } + "additionalProperties": false, + "properties": { + "type": { + "description": "Allowed time types with a tag: ABSOLUTE, BLOCK_RELATIVE, COMMAND_RELATIVE, EPOCH_RELATIVE.", + "enum": ["ABSOLUTE", "BLOCK_RELATIVE", "COMMAND_RELATIVE", "EPOCH_RELATIVE"], + "type": "string" + }, + "tag": { + "description": "Relative or absolute time. Required for ABSOLUTE, BLOCK_RELATIVE, COMMAND_RELATIVE, and EPOCH_RELATIVE time types but not COMMAND_COMPLETE.", + "type": "string" } }, - "then": { - "required": ["tag"] - } + "required": ["type", "tag"] } ], "type": "object" diff --git a/test/test.js b/test/test.js index d2de3c8..3ce32f1 100644 --- a/test/test.js +++ b/test/test.js @@ -2,32 +2,69 @@ import Ajv from 'ajv/dist/2020.js'; import schema from '../schema.json' assert { type: 'json' }; import { readdirSync, readFileSync } from 'fs'; import { exit } from 'process'; +import packageJson from '../package.json' assert { type: 'json' }; +import lockfileJson from '../package-lock.json' assert { type: 'json' }; + +function testVersions(failures) { + // Make sure version strings match + const schemaId = schema.$id; + const versionMatch = schemaId.match(/\/v(\d+\.\d+\.\d+)$/); + if(!versionMatch) throw "Cannot find version in schema.json $id field"; + const schemaVersion = versionMatch ? versionMatch[1] : null; + + const packageVersion = packageJson.version; + const lockfileVersion = lockfileJson.version; + + if(schemaVersion !== packageVersion || schemaVersion !== lockfileVersion) { + const msg = "Versions in schema.json, package.json and package-lock.json must match"; + failures.push(msg); + console.error(`❌ ${msg}`); + console.log(`schema version in schema.$id: ${schemaVersion}`); + console.log(`package.json version: ${packageVersion}`); + console.log(`package-lock.json version: ${lockfileVersion}`); + } +} function test() { - const validate = new Ajv({ strict: false }).compile(schema); + const ajv = new Ajv({ + strict: false, + verbose: true, + }); + const validate = ajv.compile(schema); const validSeqJsonPath = './test/valid-seq-json'; const validSeqJsonFiles = readdirSync(validSeqJsonPath); const invalidSeqJsonPath = './test/invalid-seq-json'; const invalidSeqJsonFiles = readdirSync(invalidSeqJsonPath); - const errors = []; + const failures = []; + + testVersions(failures); // Valid Seq JSON. for (const validSeqJsonFile of validSeqJsonFiles) { const validSeqJson = readFileSync(`${validSeqJsonPath}/${validSeqJsonFile}`).toString(); const valid = validate(JSON.parse(validSeqJson)); - if (!valid) errors.push(`${validSeqJsonFile} should be valid`); + if (!valid) { + // most relevant errors tend to be at the bottom - reverse list + const errors = validate.errors.slice().reverse(); + console.error(`❌ Failed to validate ${validSeqJsonFile} - ${errors.length} errors:`); + console.log(errors); + failures.push(`${validSeqJsonFile} should be valid`); + } } // Invalid Seq JSON. for (const invalidSeqJsonFile of invalidSeqJsonFiles) { const invalidSeqJson = readFileSync(`${invalidSeqJsonPath}/${invalidSeqJsonFile}`).toString(); const valid = validate(JSON.parse(invalidSeqJson)); - if (valid) errors.push(`${invalidSeqJsonFile} should be invalid`); + if (valid) { + console.error(`❌ Failed to invalidate ${invalidSeqJsonFile} - expected errors`); + failures.push(`${invalidSeqJsonFile} should be invalid`); + } } - if (errors.length) { - console.log('❌ Some tests failed...'); - console.error(errors); + if (failures.length) { + console.log(`❌ ${failures.length} tests failed...`); + console.error(failures); exit(1); } else { console.log('✅ All tests passed!');