diff --git a/bids-validator/src/schema/applyRules.ts b/bids-validator/src/schema/applyRules.ts index 760e44499..6c8e9083a 100644 --- a/bids-validator/src/schema/applyRules.ts +++ b/bids-validator/src/schema/applyRules.ts @@ -4,6 +4,7 @@ import { BIDSContext } from './context.ts' import { expressionFunctions } from './expressionLanguage.ts' import { logger } from '../utils/logger.ts' import { memoize } from '../utils/memoize.ts' +import { compile } from '../validators/json.ts' /** * Given a schema and context, evaluate which rules match and test them. @@ -467,7 +468,7 @@ function evalJsonCheck( return } - const validate = context.dataset.ajv.compile(metadataDef) + const validate = compile(metadataDef) const result = validate(context.sidecar[keyName]) if (result === false) { const evidenceBase = `Failed for this file.key: ${originFileKey} Schema path: ${schemaPath}` diff --git a/bids-validator/src/schema/context.ts b/bids-validator/src/schema/context.ts index 1328c0479..adf09e3bf 100644 --- a/bids-validator/src/schema/context.ts +++ b/bids-validator/src/schema/context.ts @@ -16,9 +16,6 @@ import { loadHeader } from '../files/nifti.ts' import { buildAssociations } from './associations.ts' import { ValidatorOptions } from '../setup/options.ts' import { logger } from '../utils/logger.ts' -import { Ajv, JSONSchemaType, ValidateFunction } from '../deps/ajv.ts' -import { memoize } from '../utils/memoize.ts' -import { Schema } from '../types/schema.ts' export class BIDSContextDataset implements ContextDataset { dataset_description: Record @@ -28,22 +25,15 @@ export class BIDSContextDataset implements ContextDataset { ignored: any[] modalities: any[] subjects?: ContextDatasetSubjects - ajv: Ajv sidecarKeyValidated: Set - constructor(options?: ValidatorOptions, schema?: Schema, description = {}) { + constructor(options?: ValidatorOptions, description = {}) { this.dataset_description = description this.files = [] this.tree = {} this.ignored = [] this.modalities = [] - this.ajv = new Ajv({ strictSchema: false }) - // @ts-expect-error - this.ajv.compile = memoize(this.ajv.compile) this.sidecarKeyValidated = new Set() - if (schema) { - this.setCustomAjvFormats(schema) - } if (options) { this.options = options } @@ -56,27 +46,6 @@ export class BIDSContextDataset implements ContextDataset { this.dataset_description.DatasetType = 'raw' } } - - setCustomAjvFormats(schema: Schema): void { - if (typeof schema.objects.formats !== 'object') { - // logger.warning( - console.log( - `schema.objects.formats missing from schema, format validation disabled.`, - ) - return - } - const schemaFormats = schema.objects.formats - for (let key of Object.keys(schemaFormats)) { - if (typeof schemaFormats[key]['pattern'] !== 'string') { - // logger.warning( - console.log( - `schema.objects.formats.${key} pattern missing or invalid. Skipping this format for addition to context json validator`, - ) - continue - } - this.ajv.addFormat(key, schemaFormats[key]['pattern']) - } - } } export class BIDSContextDatasetSubjects implements ContextDatasetSubjects { diff --git a/bids-validator/src/setup/loadSchema.ts b/bids-validator/src/setup/loadSchema.ts index a270f6e2f..64f20b2bc 100644 --- a/bids-validator/src/setup/loadSchema.ts +++ b/bids-validator/src/setup/loadSchema.ts @@ -3,6 +3,7 @@ import { objectPathHandler } from '../utils/objectPathHandler.ts' import * as schemaDefault from 'https://bids-specification.readthedocs.io/en/latest/schema.json' assert { type: 'json', } +import { setCustomMetadataFormats } from '../validators/json.ts' /** * Load the schema from the specification @@ -18,11 +19,12 @@ export async function loadSchema(version = 'latest'): Promise { } else if (version === 'latest' || versionRegex.test(version)) { schemaUrl = `https://bids-specification.readthedocs.io/en/${version}/schema.json` } + let schema: Schema | undefined = undefined try { const schemaModule = await import(/* @vite-ignore */ schemaUrl, { assert: { type: 'json' }, }) - return new Proxy( + schema = new Proxy( schemaModule.default as object, objectPathHandler, ) as Schema @@ -32,9 +34,11 @@ export async function loadSchema(version = 'latest'): Promise { console.error( `Warning, could not load schema from ${schemaUrl}, falling back to internal version`, ) - return new Proxy( + schema = new Proxy( schemaDefault.default as object, objectPathHandler, ) as Schema } + setCustomMetadataFormats(schema) + return schema } diff --git a/bids-validator/src/types/context.ts b/bids-validator/src/types/context.ts index 4cf43022c..f13d34041 100644 --- a/bids-validator/src/types/context.ts +++ b/bids-validator/src/types/context.ts @@ -1,5 +1,4 @@ import { ValidatorOptions } from '../setup/options.ts' -import { Ajv } from '../deps/ajv.ts' export interface ContextDatasetSubjects { sub_dirs: string[] @@ -14,7 +13,6 @@ export interface ContextDataset { modalities: any[] subjects?: ContextDatasetSubjects options?: ValidatorOptions - ajv: Ajv sidecarKeyValidated: Set } export interface ContextSubjectSessions { diff --git a/bids-validator/src/validators/bids.ts b/bids-validator/src/validators/bids.ts index ca448c99b..1f2720011 100644 --- a/bids-validator/src/validators/bids.ts +++ b/bids-validator/src/validators/bids.ts @@ -49,9 +49,9 @@ export async function validate( if (ddFile) { const description = await ddFile.text().then((text) => JSON.parse(text)) summary.dataProcessed = description.DatasetType === 'derivative' - dsContext = new BIDSContextDataset(options, schema, description) + dsContext = new BIDSContextDataset(options, description) } else { - dsContext = new BIDSContextDataset(options, schema) + dsContext = new BIDSContextDataset(options) issues.addNonSchemaIssue('MISSING_DATASET_DESCRIPTION', [] as IssueFile[]) } diff --git a/bids-validator/src/validators/json.ts b/bids-validator/src/validators/json.ts new file mode 100644 index 000000000..496f9410c --- /dev/null +++ b/bids-validator/src/validators/json.ts @@ -0,0 +1,28 @@ +import { Ajv, JSONSchemaType, ValidateFunction } from '../deps/ajv.ts' +import { Schema } from '../types/schema.ts' +import { memoize } from '../utils/memoize.ts' + +const metadataValidator = new Ajv({ strictSchema: false }) + +// Bind the method to the instance before memoizing to avoid losing the context +export const compile = memoize(metadataValidator.compile.bind(metadataValidator)) + +export function setCustomMetadataFormats(schema: Schema): void { + if (typeof schema.objects.formats !== 'object') { + logger.warning( + `schema.objects.formats missing from schema, format validation disabled.`, + ) + return + } + const schemaFormats = schema.objects.formats + for (let key of Object.keys(schemaFormats)) { + const pattern = schemaFormats[key]['pattern'] + if (typeof pattern !== 'string') { + logger.warning( + `schema.objects.formats.${key} pattern missing or invalid. Skipping this format for addition to context json validator`, + ) + continue + } + metadataValidator.addFormat(key, pattern) + } +}