Skip to content

Commit

Permalink
rf(json-schema): Factor JSON schema validation out of context.dataset
Browse files Browse the repository at this point in the history
  • Loading branch information
effigies committed Jul 30, 2024
1 parent f4de42a commit 6fd3b8b
Show file tree
Hide file tree
Showing 6 changed files with 39 additions and 39 deletions.
3 changes: 2 additions & 1 deletion bids-validator/src/schema/applyRules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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}`
Expand Down
33 changes: 1 addition & 32 deletions bids-validator/src/schema/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, unknown>
Expand All @@ -28,22 +25,15 @@ export class BIDSContextDataset implements ContextDataset {
ignored: any[]
modalities: any[]
subjects?: ContextDatasetSubjects
ajv: Ajv
sidecarKeyValidated: Set<string>

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<string>()
if (schema) {
this.setCustomAjvFormats(schema)
}
if (options) {
this.options = options
}
Expand All @@ -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 {
Expand Down
8 changes: 6 additions & 2 deletions bids-validator/src/setup/loadSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -18,11 +19,12 @@ export async function loadSchema(version = 'latest'): Promise<Schema> {
} 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
Expand All @@ -32,9 +34,11 @@ export async function loadSchema(version = 'latest'): Promise<Schema> {
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
}
2 changes: 0 additions & 2 deletions bids-validator/src/types/context.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { ValidatorOptions } from '../setup/options.ts'
import { Ajv } from '../deps/ajv.ts'

export interface ContextDatasetSubjects {
sub_dirs: string[]
Expand All @@ -14,7 +13,6 @@ export interface ContextDataset {
modalities: any[]
subjects?: ContextDatasetSubjects
options?: ValidatorOptions
ajv: Ajv
sidecarKeyValidated: Set<string>
}
export interface ContextSubjectSessions {
Expand Down
4 changes: 2 additions & 2 deletions bids-validator/src/validators/bids.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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[])
}

Expand Down
28 changes: 28 additions & 0 deletions bids-validator/src/validators/json.ts
Original file line number Diff line number Diff line change
@@ -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)
}
}

0 comments on commit 6fd3b8b

Please sign in to comment.