Skip to content

Commit

Permalink
Improve schema for Time object to generate better TS types (#35)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
dandelany authored Feb 10, 2025
1 parent a86786f commit 099c14a
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 32 deletions.
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@nasa-jpl/seq-json-schema",
"version": "1.3.0",
"version": "1.3.1",
"license": "MIT",
"type": "module",
"repository": {
Expand Down
45 changes: 23 additions & 22 deletions schema.json
Original file line number Diff line number Diff line change
@@ -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": {
Expand Down Expand Up @@ -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"
Expand Down
51 changes: 44 additions & 7 deletions test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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!');
Expand Down

0 comments on commit 099c14a

Please sign in to comment.