Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

rf(json-schema): Factor JSON schema validation out of context.dataset #2033

Merged
merged 2 commits into from
Jul 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -48,9 +48,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
29 changes: 29 additions & 0 deletions bids-validator/src/validators/json.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Ajv, JSONSchemaType, ValidateFunction } from '../deps/ajv.ts'
import { Schema } from '../types/schema.ts'
import { memoize } from '../utils/memoize.ts'
import { logger } from '../utils/logger.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)
}
}
Loading