diff --git a/src/schema/applyRules.ts b/src/schema/applyRules.ts index 568d8d48..2995c721 100644 --- a/src/schema/applyRules.ts +++ b/src/schema/applyRules.ts @@ -196,7 +196,7 @@ function evalJsonCheck( } const json: Record = sidecarRule ? context.sidecar : context.json - for (const [key, requirement] of Object.entries(rule.fields)) { + for (const [key, requirement] of Object.entries(rule.fields ?? {})) { // @ts-expect-error dynamic nested index access on GenericSchema is not typed const metadataDef = schema.objects.metadata[key] const keyName: string = metadataDef.name diff --git a/src/schema/pruneSchema.test.ts b/src/schema/pruneSchema.test.ts new file mode 100644 index 00000000..dfa32157 --- /dev/null +++ b/src/schema/pruneSchema.test.ts @@ -0,0 +1,33 @@ +import { assert } from '@std/assert' +import type { GenericRule, GenericSchema } from '../types/schema.ts' +import { loadSchema } from '../setup/loadSchema.ts' +import { pruneSchema, type SchemaFilter } from './pruneSchema.ts' + +Deno.test('Test schema pruning', async (t) => { + const schema = await loadSchema() as unknown as GenericSchema + await t.step('Test delete path', async () => { + const path = 'rules.sidecars' + const filter: SchemaFilter = {path} + assert(schema[path]) + const newSchema = pruneSchema(schema, [filter]) + assert(schema[path]) + assert(newSchema[path] == undefined) + }) + await t.step('Test delete matching rules', async () => { + const path = 'rules.sidecars' + const testPath = `${path}.derivatives.atlas.TemplateNonStandard` + const filter: SchemaFilter = { + path, + match: { + selectors: [ + 'dataset.dataset_description.DatasetType == "derivative"' + ] + } + } + assert(schema[testPath]) + const newSchema = pruneSchema(schema, [filter]) + assert(schema[path]) + assert(schema[testPath]) + assert(newSchema[testPath] == undefined) + }) +}) diff --git a/src/schema/pruneSchema.ts b/src/schema/pruneSchema.ts new file mode 100644 index 00000000..809128fd --- /dev/null +++ b/src/schema/pruneSchema.ts @@ -0,0 +1,67 @@ +import type { GenericRule, GenericSchema } from '../types/schema.ts' +import { objectPathHandler } from '../utils/objectPathHandler.ts' + +export interface SchemaFilter { + path?: string, + match?: Partial, + update?: Partial +} + +export function pruneSchema( + schema: GenericSchema, + filters: SchemaFilter[] +): GenericSchema { + let newSchema = JSON.parse(JSON.stringify(schema)) + newSchema = new Proxy( newSchema as object, objectPathHandler) as GenericSchema + filters.map(applyFilter.bind(null, newSchema)) + return newSchema +} + +function applyFilter( + schema: GenericSchema, + filter: SchemaFilter +): GenericSchema { + if (filter.path && !filter.match) { + if (filter.update) { + Object.assign(schema[filter.path], filter.update) + } else { + delete schema[filter.path] + } + return schema + } + const path = filter.path ?? 'rules' + _applyFilter(schema, filter, path) + return schema +} + +function _applyFilter( + schema: GenericSchema, + filter: SchemaFilter, + path: string +) { + if (typeof schema[path] != 'object') { + return + } + if (filter.match && ruleMatch(schema[path], filter.match)) { + if (filter.update) { + Object.assign(schema[path], filter.update) + } else { + delete schema[path] + } + } + for (const key in schema[path]) { + _applyFilter(schema, filter, `${path}.${key}`) + } + return +} + +function ruleMatch(rule: GenericRule, match: Partial): boolean { + return (Object.keys(match) as (keyof GenericRule)[]).every((key) => { + if (typeof rule[key] == 'undefined') return false + if (Array.isArray(match[key])) { + return match[key].every(entry => Array.isArray(rule[key]) && rule[key].includes(entry)) + } + // TODO matches for rule fields that are strings or records + return false + }) +} diff --git a/src/types/schema.ts b/src/types/schema.ts index 9fdcba92..d40966c7 100644 --- a/src/types/schema.ts +++ b/src/types/schema.ts @@ -78,7 +78,7 @@ export interface GenericRule { columns?: Record additional_columns?: string initial_columns?: string[] - fields: Record + fields?: Record issue?: SchemaIssue extensions?: string[] suffixes?: string[] diff --git a/src/utils/objectPathHandler.ts b/src/utils/objectPathHandler.ts index 8abaa525..6f17d72c 100644 --- a/src/utils/objectPathHandler.ts +++ b/src/utils/objectPathHandler.ts @@ -21,4 +21,23 @@ export const objectPathHandler = { } return res }, + deleteProperty(target: unknown, property: string): boolean { + if (typeof property === 'symbol') { + return true + } + const props = property.split('.') + const toDelete = props.pop() + if (toDelete === undefined) { + return true + } + let parent = target + for (const prop of props) { + if (hasProp(parent, prop)) { + parent = parent[prop] + } else { + return true + } + } + return delete (parent as Record)[toDelete] + } }