UNPKG

@finos/legend-graph

Version:
358 lines 22 kB
/** * Copyright (c) 2020-present, Goldman Sachs * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { isNonNullable, assertNonEmptyString, UnsupportedOperationError, IllegalStateError, assertNonNullable, assertTrue, LogEvent, guaranteeType, guaranteeNonNullable, } from '@finos/legend-shared'; import { Stereotype } from '../../../../../../../graph/metamodel/pure/packageableElements/domain/Stereotype.js'; import { Tag } from '../../../../../../../graph/metamodel/pure/packageableElements/domain/Tag.js'; import { Enum } from '../../../../../../../graph/metamodel/pure/packageableElements/domain/Enum.js'; import { V1_buildFullPath, } from './V1_GraphBuilderContext.js'; import { V1_buildVariable, V1_buildUnit, V1_buildTaggedValue, V1_buildFunctionSuite, } from './helpers/V1_DomainBuilderHelper.js'; import { V1_buildServiceExecution, V1_buildLegacyServiceTest, V1_buildPostValidation, V1_buildServiceOwnership, } from './helpers/V1_ServiceBuilderHelper.js'; import { V1_buildEnumerationMapping, V1_buildMappingInclude, } from './helpers/V1_MappingBuilderHelper.js'; import { V1_buildFlatDataSection } from './helpers/V1_FlatDataStoreBuilderHelper.js'; import { V1_buildSchema } from './helpers/V1_DatabaseBuilderHelper.js'; import { V1_buildConfigurationProperty, V1_buildScopeElement, } from './helpers/V1_FileGenerationBuilderHelper.js'; import { V1_buildEngineRuntime } from './helpers/V1_RuntimeBuilderHelper.js'; import { V1_buildConnection } from './helpers/V1_ConnectionBuilderHelper.js'; import { V1_ConnectionPointer } from '../../../model/packageableElements/connection/V1_ConnectionPointer.js'; import { V1_buildGenerationTreeNode, V1_buildFileGenerationPointer, } from './helpers/V1_GenerationSpecificationBuilderHelper.js'; import { V1_buildSection } from './helpers/V1_SectionBuilderHelper.js'; import { V1_buildEmbeddedData } from './helpers/V1_DataElementBuilderHelper.js'; import { V1_buildTestSuite } from './helpers/V1_TestBuilderHelper.js'; import { ServiceTestSuite } from '../../../../../../../graph/metamodel/pure/packageableElements/service/ServiceTestSuite.js'; import { V1_DataElementReference } from '../../../model/data/V1_EmbeddedData.js'; import { V1_buildFunctionSignature } from '../../../helpers/V1_DomainHelper.js'; import { getFunctionName } from '../../../../../../../graph/helpers/DomainHelper.js'; import { GraphBuilderError } from '../../../../../../GraphManagerUtils.js'; import { PostValidation } from '../../../../../../../graph/metamodel/pure/packageableElements/service/PostValidation.js'; import { V1_buildExecutionParameters } from './V1_ExecutionEnvironmentBuilderHelper.js'; import { PackageableElementExplicitReference } from '../../../../../../../graph/metamodel/pure/packageableElements/PackageableElementReference.js'; import { generateFunctionPrettyName } from '../../../../../../../graph/helpers/PureLanguageHelper.js'; import { V1_MappingIncludeMapping, } from '../../../model/packageableElements/mapping/V1_MappingInclude.js'; import { V1_INTERNAL__UnknownMappingInclude } from '../../../model/packageableElements/mapping/V1_INTERNAL__UnknownMappingInclude.js'; import { V1_buildHostedServiceDeploymentConfiguration, V1_buildSnowflakeAppDeploymentConfiguration, V1_buildDeploymentOwnership, V1_builHostedServiceOwnership, V1_buildHostedServiceActions, } from './helpers/V1_FunctionActivatorBuilderHelper.js'; import {} from '../../../model/packageableElements/dataProduct/V1_DataProduct.js'; import { AccessPointGroup } from '../../../../../../../graph/metamodel/pure/dataProduct/DataProduct.js'; import { V1_buildAccessPoint } from './helpers/V1_DataProductBuilder.js'; export class V1_ElementSecondPassBuilder { context; constructor(context) { this.context = context; } visit_PackageableElement(element) { this.context.extensions .getExtraBuilderOrThrow(element) .runSecondPass(element, this.context); } visit_INTERNAL__UnknownElement(element) { throw new UnsupportedOperationError(); } visit_INTERNAL__UnknownPackageableElement(element) { throw new UnsupportedOperationError(); } visit_IngestDefinition(element) { throw new UnsupportedOperationError(); } visit_INTERNAL__UnknownFunctionActivator(element) { const metamodel = this.context.currentSubGraph.getOwnFunctionActivator(V1_buildFullPath(element.package, element.name)); metamodel.function = PackageableElementExplicitReference.create(guaranteeNonNullable(this.context.graph.functions.find((fn) => generateFunctionPrettyName(fn, { fullPath: true, spacing: false, }) === element.function.path.replaceAll(/\s*/gu, '')))); } visit_SnowflakeApp(element) { const metamodel = this.context.currentSubGraph.getOwnFunctionActivator(V1_buildFullPath(element.package, element.name)); metamodel.function = PackageableElementExplicitReference.create(guaranteeNonNullable(this.context.graph.functions.find((fn) => generateFunctionPrettyName(fn, { fullPath: true, spacing: false, notIncludeParamName: true, }) === element.function.path.replaceAll(/\s*/gu, '')))); metamodel.ownership = V1_buildDeploymentOwnership(element.ownership, metamodel); metamodel.activationConfiguration = V1_buildSnowflakeAppDeploymentConfiguration(element.activationConfiguration, this.context); metamodel.stereotypes = element.stereotypes .map((stereotype) => this.context.resolveStereotype(stereotype)) .filter(isNonNullable); metamodel.taggedValues = element.taggedValues .map((taggedValue) => V1_buildTaggedValue(taggedValue, this.context)) .filter(isNonNullable); } visit_HostedService(element) { assertNonEmptyString(element.pattern, `Hosted Service 'pattern' field is missing or empty`); const metamodel = this.context.currentSubGraph.getOwnFunctionActivator(V1_buildFullPath(element.package, element.name)); metamodel.function = PackageableElementExplicitReference.create(guaranteeNonNullable(this.context.graph.functions.find((fn) => generateFunctionPrettyName(fn, { fullPath: true, spacing: false, notIncludeParamName: true, }) === element.function.path.replaceAll(/\s*/gu, '')))); metamodel.ownership = V1_builHostedServiceOwnership(element.ownership, metamodel); if (element.activationConfiguration) { metamodel.activationConfiguration = V1_buildHostedServiceDeploymentConfiguration(element.activationConfiguration); } metamodel.stereotypes = element.stereotypes .map((stereotype) => this.context.resolveStereotype(stereotype)) .filter(isNonNullable); metamodel.taggedValues = element.taggedValues .map((taggedValue) => V1_buildTaggedValue(taggedValue, this.context)) .filter(isNonNullable); metamodel.actions = element.actions .map((action) => V1_buildHostedServiceActions(action, this.context)) .filter(isNonNullable); } visit_INTERNAL__UnknownStore(element) { return; } visit_Profile(element) { const profile = this.context.currentSubGraph.getOwnProfile(V1_buildFullPath(element.package, element.name)); const uniqueStereotypes = new Set(); profile.p_stereotypes = element.stereotypes.map((stereotypeV) => { const stereotype = stereotypeV.value; if (uniqueStereotypes.has(stereotype)) { const message = `Found duplicated stereotype '${stereotype}' in profile '${element.path}'`; /** * In strict-mode, graph builder will consider this as an error * See https://github.com/finos/legend-studio/issues/941 * * @discrepancy graph-building */ if (this.context.options?.strict) { throw new GraphBuilderError(message); } this.context.logService.warn(LogEvent.create(message)); } uniqueStereotypes.add(stereotype); return new Stereotype(profile, stereotype); }); const uniqueTags = new Set(); profile.p_tags = element.tags.map((tagV) => { const tag = tagV.value; if (uniqueTags.has(tag)) { const message = `Found duplicated tag '${tag}' in profile '${element.path}'`; /** * In strict-mode, graph builder will consider this as an error * See https://github.com/finos/legend-studio/issues/941 * * @discrepancy graph-building */ if (this.context.options?.strict) { throw new GraphBuilderError(message); } this.context.logService.warn(LogEvent.create(message)); } uniqueTags.add(tag); return new Tag(profile, tag); }); } visit_Enumeration(element) { const enumeration = this.context.currentSubGraph.getOwnEnumeration(V1_buildFullPath(element.package, element.name)); enumeration.stereotypes = element.stereotypes .map((stereotype) => this.context.resolveStereotype(stereotype)) .filter(isNonNullable); enumeration.taggedValues = element.taggedValues .map((taggedValue) => V1_buildTaggedValue(taggedValue, this.context)) .filter(isNonNullable); const uniqueEnumValues = new Set(); enumeration.values = element.values.map((enumValue) => { assertNonEmptyString(enumValue.value, `Enum value 'value' field is missing or empty`); if (uniqueEnumValues.has(enumValue.value)) { const message = `Found duplicated value '${enumValue.value}' in enumeration '${enumeration.path}'`; /** * In strict-mode, graph builder will consider this as an error * See https://github.com/finos/legend-studio/issues/941 * * @discrepancy graph-building */ if (this.context.options?.strict) { throw new GraphBuilderError(message); } this.context.logService.warn(LogEvent.create(message)); } const _enum = new Enum(enumValue.value, enumeration); _enum.stereotypes = enumValue.stereotypes .map((stereotype) => this.context.resolveStereotype(stereotype)) .filter(isNonNullable); _enum.taggedValues = enumValue.taggedValues .map((taggedValue) => V1_buildTaggedValue(taggedValue, this.context)) .filter(isNonNullable); uniqueEnumValues.add(enumValue.value); return _enum; }); } visit_Measure(element) { assertNonNullable(element.canonicalUnit, `Measure 'canonicalUnit' field is missing`); const measure = this.context.currentSubGraph.getOwnMeasure(V1_buildFullPath(element.package, element.name)); measure.canonicalUnit = V1_buildUnit(element.canonicalUnit, measure, this.context.graph, this.context); measure.nonCanonicalUnits = element.nonCanonicalUnits.map((unit) => V1_buildUnit(unit, measure, this.context.currentSubGraph, this.context)); } visit_Class(element) { const _class = this.context.currentSubGraph.getOwnClass(V1_buildFullPath(element.package, element.name)); _class.stereotypes = element.stereotypes .map((stereotype) => this.context.resolveStereotype(stereotype)) .filter(isNonNullable); _class.taggedValues = element.taggedValues .map((taggedValue) => V1_buildTaggedValue(taggedValue, this.context)) .filter(isNonNullable); } visit_Association(element) { throw new UnsupportedOperationError(); } // NOTE: Alloy Execution server has 2 passes for processing as it needs to build the lambda and compiles // but we currently do not build the lambda and so this needs only one pass visit_ConcreteFunctionDefinition(protocol) { assertNonNullable(protocol.returnGenericType, `Function 'returnGenericType' field is missing or empty`); assertNonNullable(protocol.returnMultiplicity, `Function 'returnMultiplicity' field is missing`); const func = this.context.currentSubGraph.getOwnFunction(V1_buildFullPath(protocol.package, V1_buildFunctionSignature(protocol))); func.returnType = this.context.resolveGenericTypeFromProtocol(protocol.returnGenericType); func.returnMultiplicity = this.context.graph.getMultiplicity(protocol.returnMultiplicity.lowerBound, protocol.returnMultiplicity.upperBound); func.stereotypes = protocol.stereotypes .map((stereotype) => this.context.resolveStereotype(stereotype)) .filter(isNonNullable); func.taggedValues = protocol.taggedValues .map((taggedValue) => V1_buildTaggedValue(taggedValue, this.context)) .filter(isNonNullable); func.parameters = protocol.parameters.map((param) => V1_buildVariable(param, this.context)); func.expressionSequence = protocol.body; func.functionName = getFunctionName(func, func.name); if (protocol.tests?.length) { func.tests = protocol.tests.map((l) => V1_buildFunctionSuite(l, this.context)); } } visit_FlatData(element) { const flatData = this.context.currentSubGraph.getOwnFlatDataStore(V1_buildFullPath(element.package, element.name)); flatData.sections = element.sections.map((section) => V1_buildFlatDataSection(section, flatData, this.context)); } visit_Database(element) { const database = this.context.currentSubGraph.getOwnDatabase(V1_buildFullPath(element.package, element.name)); database.includes = element.includedStores.map((includedStore) => this.context.resolveDatabase(includedStore.path)); database.schemas = element.schemas.map((schema) => V1_buildSchema(schema, database, this.context)); } getMappingIdentifier(protocol) { if (protocol instanceof V1_INTERNAL__UnknownMappingInclude) { return undefined; } if (protocol instanceof V1_MappingIncludeMapping) { return protocol.includedMapping; } const extraIncludeMappingIdentifierBuilders = this.context.extensions.plugins.flatMap((plugin) => plugin.V1_getExtraMappingIncludeIdentifierBuilders?.() ?? []); for (const builder of extraIncludeMappingIdentifierBuilders) { const builtIdentifier = builder(protocol); if (builtIdentifier) { return builtIdentifier; } } throw new UnsupportedOperationError(`Can't build mapping include identifier: no compatible builder available from plugins`, protocol); } visit_Mapping(element) { const mapping = this.context.currentSubGraph.getOwnMapping(V1_buildFullPath(element.package, element.name)); const mappingIncludesSet = new Set(); mapping.includes = element.includedMappings.map((mappingInclude) => { const includedMappingIdentifier = this.getMappingIdentifier(mappingInclude); if (includedMappingIdentifier) { assertTrue(!mappingIncludesSet.has(includedMappingIdentifier), `Duplicated mapping include '${includedMappingIdentifier}' in mapping '${mapping.path}'`); mappingIncludesSet.add(includedMappingIdentifier); } return V1_buildMappingInclude(mappingInclude, this.context, mapping); }); mapping.enumerationMappings = element.enumerationMappings.map((enumerationMapping) => V1_buildEnumerationMapping(enumerationMapping, this.context, mapping)); } visit_Service(element) { assertNonEmptyString(element.pattern, `Service 'pattern' field is missing or empty`); const service = this.context.currentSubGraph.getOwnService(V1_buildFullPath(element.package, element.name)); service.stereotypes = element.stereotypes .map((stereotype) => this.context.resolveStereotype(stereotype)) .filter(isNonNullable); service.taggedValues = element.taggedValues .map((taggedValue) => V1_buildTaggedValue(taggedValue, this.context)) .filter(isNonNullable); service.pattern = element.pattern; service.owners = element.owners.slice(); service.documentation = element.documentation; service.autoActivateUpdates = element.autoActivateUpdates; if (element.ownership) { service.ownership = V1_buildServiceOwnership(element.ownership, service); } // NOTE: process execution before the test, so we can do some check between test and execution (such matching type, keys, etc.) service.execution = V1_buildServiceExecution(element.execution, this.context, service); if (element.test) { service.test = V1_buildLegacyServiceTest(element.test, this.context, service); } service.tests = element.testSuites .map((testSuite) => V1_buildTestSuite(service, testSuite, this.context)) .map((e) => guaranteeType(e, ServiceTestSuite)); service.postValidations = element.postValidations .map((postValidation) => V1_buildPostValidation(postValidation, this.context)) .map((e) => guaranteeType(e, PostValidation)); } visit_SectionIndex(element) { const sectionIndex = this.context.currentSubGraph.getOwnSectionIndex(element.path); sectionIndex.sections = element.sections.map((section) => V1_buildSection(section, this.context, sectionIndex)); } visit_FileGeneration(element) { assertNonEmptyString(element.type, `File generation 'type' field is missing or empty`); const fileGeneration = this.context.currentSubGraph.getOwnFileGeneration(V1_buildFullPath(element.package, element.name)); fileGeneration.type = element.type; fileGeneration.configurationProperties = element.configurationProperties.map(V1_buildConfigurationProperty); fileGeneration.generationOutputPath = element.generationOutputPath; fileGeneration.scopeElements = element.scopeElements.map((scopeElement) => V1_buildScopeElement(scopeElement, this.context)); } visit_GenerationSpecification(element) { const generationSpec = this.context.currentSubGraph.getOwnGenerationSpecification(V1_buildFullPath(element.package, element.name)); generationSpec.generationNodes = element.generationNodes.map((node) => V1_buildGenerationTreeNode(node, this.context)); generationSpec.fileGenerations = element.fileGenerations.map((node) => V1_buildFileGenerationPointer(node, this.context)); } visit_PackageableRuntime(element) { const runtime = this.context.currentSubGraph.getOwnRuntime(V1_buildFullPath(element.package, element.name)); runtime.runtimeValue = V1_buildEngineRuntime(element.runtimeValue, this.context); } visit_PackageableConnection(element) { const connection = this.context.currentSubGraph.getOwnConnection(V1_buildFullPath(element.package, element.name)); if (element.connectionValue instanceof V1_ConnectionPointer) { throw new IllegalStateError('Packageable connection value cannot be a connection pointer'); } connection.connectionValue = V1_buildConnection(element.connectionValue, this.context); } visit_DataElement(element) { const dataElement = this.context.currentSubGraph.getOwnDataElement(V1_buildFullPath(element.package, element.name)); dataElement.stereotypes = element.stereotypes .map((stereotype) => this.context.resolveStereotype(stereotype)) .filter(isNonNullable); dataElement.taggedValues = element.taggedValues .map((taggedValue) => V1_buildTaggedValue(taggedValue, this.context)) .filter(isNonNullable); if (element.data instanceof V1_DataElementReference) { throw new IllegalStateError('Cannot use Data element reference in a Data element'); } dataElement.data = V1_buildEmbeddedData(element.data, this.context); } visit_ExecutionEnvironmentInstance(element) { const execEnv = this.context.currentSubGraph.getOwnExecutionEnvironment(V1_buildFullPath(element.package, element.name)); execEnv.executionParameters = element.executionParameters.map((e) => V1_buildExecutionParameters(e, this.context)); } visit_DataProduct(element) { const dataProduct = this.context.currentSubGraph.getOwnDataProduct(V1_buildFullPath(element.package, element.name)); dataProduct.title = element.title; dataProduct.description = element.description; dataProduct.accessPointGroups = element.accessPointGroups.map((elementGroup) => { const group = new AccessPointGroup(); group.id = elementGroup.id; group.description = elementGroup.description; group.accessPoints = elementGroup.accessPoints.map((ep) => V1_buildAccessPoint(ep, this.context)); return group; }); } } //# sourceMappingURL=V1_ElementSecondPassBuilder.js.map