From f296385d36f195af4ec417c9b34ddaf81d4ef05c Mon Sep 17 00:00:00 2001 From: Jakob Voss Date: Thu, 14 Dec 2023 21:00:29 +0100 Subject: [PATCH] Extend schema validator --- lib/schema-validator.js | 88 +++++++++++++++++++++++++++++------------ test/schema-suite.json | 34 ++++++++++++++-- 2 files changed, 93 insertions(+), 29 deletions(-) diff --git a/lib/schema-validator.js b/lib/schema-validator.js index af6c71c..6639ad5 100644 --- a/lib/schema-validator.js +++ b/lib/schema-validator.js @@ -21,25 +21,17 @@ export class SchemaValidator { } } - validateViaSchema(schema) { + validateWithJSONSchema(schema) { return this.validator(schema) ? [] : this.validator.errors } validate(schema) { - const errors = this.validator ? this.validateViaSchema(schema) : [] + const errors = this.validator ? this.validateWithJSONSchema(schema) : [] - if (typeof schema.fields === "object") { - errors.push(...this.validateFieldSchedule(schema.fields)) - } else if (!this.validator) { - errors.push({ message: "missing or malformed required key 'fields'" }) - } + errors.push(...this.validateFieldSchedule(schema.fields)) - if (typeof schema.codelists === "object") { - for (let codelist of Object.values(schema.codelists)) { - if (typeof codelist === "object") { - errors.push(...this.validateCodes(codelist.codes || {})) - } - } + for (let codelist of Object.values(schema.codelists || {})) { + errors.push(...this.validateCodes(codelist.codes || {})) } return errors @@ -49,28 +41,72 @@ export class SchemaValidator { const errors = [] for (let id in fields) { const field = fields[id] - if (typeof field === "object") { - if ("tag" in field) { - let hasId = field.tag - + ("occurrence" in field ? "/" + field.occurrence : "") - + ("counter" in field ? "/$x" + field.counter : "") - if (hasId !== id) { - errors.push({ - message: `field identifier '${id}' does not match field definition '${hasId}'`, - }) + if ("tag" in field) { + let hasId = field.tag + + ("occurrence" in field ? "/" + field.occurrence : "") + + ("counter" in field ? "/$x" + field.counter : "") + if (hasId !== id) { + errors.push({ + message: `field identifier '${id}' does not match field definition '${hasId}'`, + }) + } + } + for (let ind of ["indicator1","indicator2"]) { + if (ind in field && typeof field[ind].codes == "object") { + errors.push(...this.validateCodes(field[ind].codes || {})) + } + } + if (field.subfields) { + for (let key of ["positions", "pattern", "codes"]) { + if (key in field) { + errors.push({ message: `variable field must not have ${key}` }) } } - } else if (!this.validator) { - errors.push({ message: "field definition must be object" }) + errors.push(...this.validateSubfieldSchedule(field.subfields)) + } else { + errors.push( + ...this.validateCodes(field.codes || {}), + ...this.validatePositions(field.positions || {}), + ...this.validatePattern(field.pattern), + ) } } - // TODO: additional constraints + return errors + } + + validateSubfieldSchedule(subfields) { + // TODO: not covered by test suite + const errors = [] + for (let code in subfields) { + const sf = subfields[code] + if ("code" in sf && sf.code !== code) { + errors.push({ message: `subfield code '${sf.code}' must be '${code}'` }) + } + errors.push( + ...this.validateCodes(sf.codes || {}), + ...this.validatePositions(sf.positions || {}), + ...this.validatePattern(sf.pattern), + ) + } return errors } validateCodes(codes) { return Object.entries(codes) .filter(([key, code]) => "code" in code && code.code !== key) - .map(([key]) => ({ message: `code must be '${key}' in codelist` })) + .map(([key, code]) => ({ message: `code '${code.code}' must be '${key}' in codelist` })) + } + + validatePositions(/*positions*/) { + return [] // TODO + } + + validatePattern(pattern) { + if (pattern != null) { + try { new RegExp(pattern) } catch (e) { + return [{ message: `Invalid pattern '${pattern}'` }] + } + } + return [] } } diff --git a/test/schema-suite.json b/test/schema-suite.json index e53f0b7..fc37754 100644 --- a/test/schema-suite.json +++ b/test/schema-suite.json @@ -1,7 +1,11 @@ { "valid": [ { - "fields": {}, + "fields": { + "number": { + "pattern": "^[0-9]+$" + } + }, "codelists": { "languages": { "title": "langauges", @@ -49,7 +53,13 @@ { "schema": { "fields": { - "abc": { "tag": "xyz" } + "abc": { + "tag": "xyz", + "indicator1": { }, + "indicator2": { + "codes": { "a": { "code": "b" } } + } + } }, "codelists": { "test": { "codes": { "x": {}, "y": { "code": "z" } } } @@ -57,7 +67,25 @@ }, "errors": [ "field identifier 'abc' does not match field definition 'xyz'", - "code must be 'y' in codelist" + "code 'b' must be 'a' in codelist", + "code 'z' must be 'y' in codelist" + ] + }, + { + "schema": { + "fields": { + "abc": { + "subfields": { "x": {} }, + "codes": {} + }, + "number": { + "pattern": "[" + } + } + }, + "errors": [ + "variable field must not have codes", + "Invalid pattern '['" ] } ]