UNPKG

@itwin/ecschema-metadata

Version:

ECObjects core concepts in typescript

110 lines 4.97 kB
/*--------------------------------------------------------------------------------------------- * Copyright (c) Bentley Systems, Incorporated. All rights reserved. * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ import { SchemaMatchType } from "../ECObjects"; import { ECSchemaError, ECSchemaStatus } from "../Exception"; /** * Utility class for detecting cyclic references in a Schema graph. * @internal */ export class SchemaGraph { _schemas = []; constructor() { } find(schemaKey) { return this._schemas.find((info) => info.schemaKey.matches(schemaKey, SchemaMatchType.Latest)); } /** * Detected cyclic references in a schema and throw an exception if a cycle is found. */ throwIfCycles() { const cycles = this.detectCycles(); if (cycles) { const result = cycles.map((cycle) => `${cycle.schema.schemaKey.name} --> ${cycle.refSchema.schemaKey.name}`).join(", "); throw new ECSchemaError(ECSchemaStatus.InvalidECJson, `Schema '${this._schemas[0].schemaKey.name}' has reference cycles: ${result}`); } } /** * Detected cyclic references in a schema. * @returns An array describing the cycle if there is a cycle or undefined if no cycles found. */ detectCycles() { const visited = {}; const recStack = {}; const cycles = []; for (const schema of this._schemas) { if (this.detectCycleUtil(schema, visited, recStack, cycles)) { return cycles.length > 0 ? cycles : undefined; } } return undefined; } detectCycleUtil(schema, visited, recStack, cycles) { let cycleFound = false; if (!visited[schema.schemaKey.name]) { visited[schema.schemaKey.name] = true; recStack[schema.schemaKey.name] = true; for (const refKey of schema.references) { const refSchema = this.find(refKey.schemaKey); if (undefined === refSchema) throw new ECSchemaError(ECSchemaStatus.UnableToLoadSchema, `Could not find the schema info for ref schema ${refKey.schemaKey.toString()} for schema ${schema.schemaKey.toString()}`); if (!visited[refKey.schemaKey.name] && this.detectCycleUtil(refSchema, visited, recStack, cycles)) { cycles.push({ schema, refSchema }); cycleFound = true; } else if (recStack[refKey.schemaKey.name]) { cycles.push({ schema, refSchema }); cycleFound = true; } } } if (!cycleFound) recStack[schema.schemaKey.name] = false; return cycleFound; } /** * Generates a SchemaGraph for the input schema using the context to find info on referenced schemas. Use the generateGraphSync if you have the fully loaded Schema. * @param schema The SchemaInfo to build the graph from * @param context The SchemaContext used to locate info on the referenced schemas * @returns A SchemaGraph that can be used to detect schema cycles */ static async generateGraph(schema, context) { const graph = new SchemaGraph(); const genGraph = async (s) => { if (graph.find(s.schemaKey)) return; graph._schemas.push(s); for (const refSchema of s.references) { if (!graph.find(refSchema.schemaKey)) { const refInfo = await context.getSchemaInfo(refSchema.schemaKey, SchemaMatchType.LatestWriteCompatible); if (undefined === refInfo) { throw new ECSchemaError(ECSchemaStatus.UnableToLocateSchema, `Could not locate the referenced schema, ${refSchema.schemaKey.name}.${refSchema.schemaKey.version.toString()}, of ${s.schemaKey.name} when populating the graph for ${schema.schemaKey.name}`); } await genGraph(refInfo); } } }; await genGraph(schema); return graph; } /** * Generates a SchemaGraph for the input schema. Use the generateGraph if you just have schema info. * @param schema The Schema to build the graph from. * @returns A SchemaGraph that can be used to detect schema cycles */ static generateGraphSync(schema) { const graph = new SchemaGraph(); const genGraph = (s) => { if (graph.find(s.schemaKey)) return; graph._schemas.push(s); for (const refSchema of s.references) { if (!graph.find(refSchema.schemaKey)) genGraph(refSchema); } }; genGraph(schema); return graph; } } //# sourceMappingURL=SchemaGraph.js.map