@finos/legend-extension-dsl-data-space
Version:
Legend extension for Data Space DSL
717 lines (697 loc) • 27.9 kB
text/typescript
/**
* 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 packageJson from '../../../../package.json' with { type: 'json' };
import V1_SYSTEM_MODELS from './v1/V1_DSL_DataSpace_SystemModels.json' with { type: 'json' };
import {
V1_DataSpace,
V1_DataSpaceDiagram,
V1_DataSpaceElementPointer,
V1_DataSpaceExecutionContext,
V1_DataSpacePackageableElementExecutable,
V1_DataSpaceSupportCombinedInfo,
V1_DataSpaceSupportEmail,
V1_DataSpaceTemplateExecutable,
} from './v1/model/packageableElements/dataSpace/V1_DSL_DataSpace_DataSpace.js';
import {
type PlainObject,
guaranteeNonEmptyString,
guaranteeNonNullable,
UnsupportedOperationError,
isNonNullable,
assertType,
guaranteeType,
returnUndefOnError,
} from '@finos/legend-shared';
import { deserialize } from 'serializr';
import {
V1_DATA_SPACE_ELEMENT_PROTOCOL_TYPE,
V1_deserializeDataSpace,
V1_MAPPING_INCLUDE_DATASPACE_TYPE,
V1_serializeDataSpace,
V1_serializeMappingInclude,
V1_deserializeMappingInclude,
} from './v1/transformation/pureProtocol/V1_DSL_DataSpace_ProtocolHelper.js';
import { V1_resolveDataSpace } from './v1/transformation/pureGraph/V1_DSL_DataSpace_GraphBuilderHelper.js';
import { getOwnDataSpace } from '../../DSL_DataSpace_GraphManagerHelper.js';
import {
type DataSpaceElement,
DataSpace,
DataSpaceExecutionContext,
DataSpaceSupportCombinedInfo,
DataSpaceSupportEmail,
DataSpaceDiagram,
DataSpaceElementPointer,
DataSpaceExecutableTemplate,
DataSpacePackageableElementExecutable,
} from '../../../graph/metamodel/pure/model/packageableElements/dataSpace/DSL_DataSpace_DataSpace.js';
import {
type PackageableElement,
type V1_ElementProtocolClassifierPathGetter,
type V1_ElementProtocolDeserializer,
type V1_ElementProtocolSerializer,
type V1_ElementTransformer,
type V1_GraphBuilderContext,
type V1_GraphTransformerContext,
type V1_PackageableElement,
type V1_PureModelContextData,
type V1_TaggedValue,
type Mapping,
type MappingInclude,
type PackageableElementReference,
type V1_MappingInclude,
type DSL_Mapping_PureProtocolProcessorPlugin_Extension,
type V1_MappingIncludeBuilder,
type V1_MappingIncludeTransformer,
type V1_MappingIncludeProtocolSerializer,
type V1_MappingIncludeProtocolDeserializer,
type V1_MappingIncludeIdentifierBuilder,
type QueryExecutionContextInfo,
type V1_SavedQueryExecutionBuilder,
type V1_ElementPointerType,
type V1_RawLambda,
V1_taggedValueModelSchema,
PackageableElementExplicitReference,
V1_PackageableElementPointer,
PackageableElementPointerType,
V1_buildTaggedValue,
V1_transformStereotype,
V1_transformTaggedValue,
PureProtocolProcessorPlugin,
V1_ElementBuilder,
V1_initPackageableElement,
V1_buildFullPath,
Class,
Enumeration,
Association,
Package,
V1_RawValueSpecificationTransformer,
V1_buildRawLambdaWithResolvedPaths,
V1_buildEmbeddedData,
V1_transformEmbeddedData,
DataElementReference,
V1_DataElementReference,
QueryDataSpaceExecutionContextInfo,
generateFunctionPrettyName,
ConcreteFunctionDefinition,
} from '@finos/legend-graph';
import { V1_resolveDiagram } from '@finos/legend-extension-dsl-diagram/graph';
import { V1_MappingIncludeDataSpace } from './v1/model/packageableElements/mapping/V1_DSL_DataSpace_MappingIncludeDataSpace.js';
import { MappingIncludeDataSpace } from '../../../graph/metamodel/pure/model/packageableElements/mapping/DSL_DataSpace_MappingIncludeDataSpace.js';
import type { Entity } from '@finos/legend-storage';
export const DATA_SPACE_ELEMENT_CLASSIFIER_PATH =
'meta::pure::metamodel::dataSpace::DataSpace';
export const DATA_SPACE_ELEMENT_POINTER = 'DATASPACE';
export class DSL_DataSpace_PureProtocolProcessorPlugin
extends PureProtocolProcessorPlugin
implements DSL_Mapping_PureProtocolProcessorPlugin_Extension
{
constructor() {
super(
packageJson.extensions.pureProtocolProcessorPlugin,
packageJson.version,
);
}
override V1_getExtraElementBuilders(): V1_ElementBuilder<V1_PackageableElement>[] {
return [
new V1_ElementBuilder<V1_DataSpace>({
elementClassName: 'DataSpace',
_class: V1_DataSpace,
firstPass: (
elementProtocol: V1_PackageableElement,
context: V1_GraphBuilderContext,
): PackageableElement => {
assertType(elementProtocol, V1_DataSpace);
const element = new DataSpace(elementProtocol.name);
const path = V1_buildFullPath(
elementProtocol.package,
elementProtocol.name,
);
element.executionContexts = guaranteeNonNullable(
elementProtocol.executionContexts,
`Data product 'executionContexts' field is missing`,
).map((contextProtocol) => {
const execContext = new DataSpaceExecutionContext();
execContext.name = guaranteeNonEmptyString(
contextProtocol.name,
`Data product execution context 'name' field is missing or empty`,
);
execContext.title = contextProtocol.title;
execContext.description = contextProtocol.description;
execContext.mapping = PackageableElementExplicitReference.create(
context.graph.getMapping(contextProtocol.mapping.path),
);
execContext.defaultRuntime =
PackageableElementExplicitReference.create(
context.graph.getRuntime(contextProtocol.defaultRuntime.path),
);
execContext.testData = contextProtocol.testData
? guaranteeType(
V1_buildEmbeddedData(contextProtocol.testData, context),
DataElementReference,
)
: undefined;
return execContext;
});
element.defaultExecutionContext = guaranteeNonNullable(
element.executionContexts.find(
(execContext) =>
execContext.name ===
guaranteeNonEmptyString(
elementProtocol.defaultExecutionContext,
`Data product 'defaultExecutionContext' field is missing or empty`,
),
),
`Can't find default execution context '${elementProtocol.defaultExecutionContext}'`,
);
context.currentSubGraph.setOwnElementInExtension(
path,
element,
DataSpace,
);
return element;
},
secondPass: (
elementProtocol: V1_PackageableElement,
context: V1_GraphBuilderContext,
): void => {
assertType(elementProtocol, V1_DataSpace);
const path = V1_buildFullPath(
elementProtocol.package,
elementProtocol.name,
);
const element = getOwnDataSpace(path, context.currentSubGraph);
element.stereotypes = elementProtocol.stereotypes
.map((stereotype) => context.resolveStereotype(stereotype))
.filter(isNonNullable);
element.taggedValues = elementProtocol.taggedValues
.map((taggedValue) => V1_buildTaggedValue(taggedValue, context))
.filter(isNonNullable);
element.title = elementProtocol.title;
element.description = elementProtocol.description;
if (elementProtocol.elements) {
element.elements = elementProtocol.elements.map((pointer) => {
const elementReference = context.resolveElement(
pointer.path,
true,
);
if (
elementReference.value instanceof Package ||
elementReference.value instanceof Class ||
elementReference.value instanceof Enumeration ||
elementReference.value instanceof Association
) {
const elementPointer = new DataSpaceElementPointer();
elementPointer.element =
elementReference as unknown as PackageableElementReference<DataSpaceElement>;
elementPointer.exclude = pointer.exclude;
return elementPointer;
}
throw new UnsupportedOperationError(
`Can't find data product element (only allow packages, classes, enumerations, and associations) '${pointer.path}'`,
);
});
}
if (elementProtocol.executables) {
element.executables = elementProtocol.executables.map(
(executableProtocol) => {
if (
executableProtocol instanceof V1_DataSpaceTemplateExecutable
) {
const executable = new DataSpaceExecutableTemplate();
if (executableProtocol.id) {
executable.id = executableProtocol.id;
}
executable.title = executableProtocol.title;
executable.description = executableProtocol.description;
executable.query = V1_buildRawLambdaWithResolvedPaths(
executableProtocol.query.parameters,
executableProtocol.query.body,
context,
);
if (executableProtocol.executionContextKey) {
executable.executionContextKey =
executableProtocol.executionContextKey;
}
return executable;
} else if (
executableProtocol instanceof
V1_DataSpacePackageableElementExecutable
) {
const executable =
new DataSpacePackageableElementExecutable();
if (executableProtocol.id) {
executable.id = executableProtocol.id;
}
executable.title = executableProtocol.title;
executable.description = executableProtocol.description;
if (executableProtocol.executionContextKey) {
executable.executionContextKey =
executableProtocol.executionContextKey;
}
try {
executable.executable = context.resolveElement(
executableProtocol.executable.path,
false,
);
} catch {
try {
executable.executable =
PackageableElementExplicitReference.create(
guaranteeNonNullable(
context.graph.functions.find(
(fn) =>
generateFunctionPrettyName(fn, {
fullPath: true,
spacing: false,
notIncludeParamName: true,
}) ===
executableProtocol.executable.path.replaceAll(
/\s*/gu,
'',
),
),
),
);
} catch {
throw new UnsupportedOperationError(
`Can't analyze data product executable with element in path: ${executableProtocol.executable.path}`,
executableProtocol,
);
}
}
return executable;
} else {
throw new UnsupportedOperationError(
`Can't build data product executable`,
executableProtocol,
);
}
},
);
}
if (elementProtocol.diagrams) {
element.diagrams = elementProtocol.diagrams.map(
(diagramProtocol) => {
const diagram = new DataSpaceDiagram();
diagram.title = diagramProtocol.title;
diagram.description = diagramProtocol.description;
diagram.diagram = V1_resolveDiagram(
diagramProtocol.diagram.path,
context,
);
return diagram;
},
);
}
if (elementProtocol.supportInfo) {
if (
elementProtocol.supportInfo instanceof V1_DataSpaceSupportEmail
) {
const supportEmail = new DataSpaceSupportEmail();
supportEmail.documentationUrl =
elementProtocol.supportInfo.documentationUrl;
supportEmail.address = guaranteeNonEmptyString(
elementProtocol.supportInfo.address,
`Data product support email 'address' field is missing or empty`,
);
element.supportInfo = supportEmail;
} else if (
elementProtocol.supportInfo instanceof
V1_DataSpaceSupportCombinedInfo
) {
const combinedInfo = new DataSpaceSupportCombinedInfo();
combinedInfo.documentationUrl =
elementProtocol.supportInfo.documentationUrl;
combinedInfo.emails = elementProtocol.supportInfo.emails;
combinedInfo.website = elementProtocol.supportInfo.website;
combinedInfo.faqUrl = elementProtocol.supportInfo.faqUrl;
combinedInfo.supportUrl = elementProtocol.supportInfo.supportUrl;
element.supportInfo = combinedInfo;
} else {
throw new UnsupportedOperationError(
`Can't build data product support info`,
elementProtocol.supportInfo,
);
}
}
},
}),
];
}
override V1_getExtraSystemModels(): PlainObject<V1_PureModelContextData>[] {
return [V1_SYSTEM_MODELS];
}
override V1_getExtraElementClassifierPathGetters(): V1_ElementProtocolClassifierPathGetter[] {
return [
(elementProtocol: V1_PackageableElement): string | undefined => {
if (elementProtocol instanceof V1_DataSpace) {
return DATA_SPACE_ELEMENT_CLASSIFIER_PATH;
}
return undefined;
},
];
}
override V1_getExtraElementPointerTypes(): V1_ElementPointerType[] {
return [
(elementProtocol: PackageableElement): string | undefined => {
if (elementProtocol instanceof DataSpace) {
return DATA_SPACE_ELEMENT_POINTER;
}
return undefined;
},
];
}
override V1_getExtraElementProtocolSerializers(): V1_ElementProtocolSerializer[] {
return [
(
elementProtocol: V1_PackageableElement,
plugins: PureProtocolProcessorPlugin[],
): PlainObject<V1_PackageableElement> | undefined => {
if (elementProtocol instanceof V1_DataSpace) {
return V1_serializeDataSpace(elementProtocol);
}
return undefined;
},
];
}
override V1_getExtraElementProtocolDeserializers(): V1_ElementProtocolDeserializer[] {
return [
(
json: PlainObject<V1_PackageableElement>,
plugins: PureProtocolProcessorPlugin[],
): V1_PackageableElement | undefined => {
if (json._type === V1_DATA_SPACE_ELEMENT_PROTOCOL_TYPE) {
return V1_deserializeDataSpace(json);
}
return undefined;
},
];
}
override V1_getExtraElementTransformers(): V1_ElementTransformer[] {
return [
(
metamodel: PackageableElement,
context: V1_GraphTransformerContext,
): V1_PackageableElement | undefined => {
if (metamodel instanceof DataSpace) {
const protocol = new V1_DataSpace();
V1_initPackageableElement(protocol, metamodel);
protocol.stereotypes = metamodel.stereotypes.map(
V1_transformStereotype,
);
protocol.taggedValues = metamodel.taggedValues.map(
V1_transformTaggedValue,
);
protocol.executionContexts = metamodel.executionContexts.map(
(execContext) => {
const contextProtocol = new V1_DataSpaceExecutionContext();
contextProtocol.name = execContext.name;
contextProtocol.title = execContext.title;
contextProtocol.description = execContext.description;
contextProtocol.mapping = new V1_PackageableElementPointer(
PackageableElementPointerType.MAPPING,
execContext.mapping.valueForSerialization ?? '',
);
contextProtocol.defaultRuntime = new V1_PackageableElementPointer(
PackageableElementPointerType.RUNTIME,
execContext.defaultRuntime.valueForSerialization ?? '',
);
contextProtocol.testData = execContext.testData
? guaranteeType(
V1_transformEmbeddedData(execContext.testData, context),
V1_DataElementReference,
)
: undefined;
return contextProtocol;
},
);
protocol.defaultExecutionContext =
metamodel.defaultExecutionContext.name;
protocol.title = metamodel.title;
protocol.description = metamodel.description;
protocol.elements = metamodel.elements?.map((pointer) => {
const elementPointer = new V1_DataSpaceElementPointer();
elementPointer.exclude = pointer.exclude;
elementPointer.path = pointer.element.valueForSerialization ?? '';
return elementPointer;
});
protocol.executables = metamodel.executables?.map((executable) => {
if (executable instanceof DataSpaceExecutableTemplate) {
const executableProtocol = new V1_DataSpaceTemplateExecutable();
if (executable.id) {
executableProtocol.id = executable.id;
}
executableProtocol.title = executable.title;
executableProtocol.description = executable.description;
if (executable.executionContextKey) {
executableProtocol.executionContextKey =
executable.executionContextKey;
}
executableProtocol.query =
executable.query.accept_RawValueSpecificationVisitor(
new V1_RawValueSpecificationTransformer(context),
) as V1_RawLambda;
return executableProtocol;
} else if (
executable instanceof DataSpacePackageableElementExecutable
) {
const executableProtocol =
new V1_DataSpacePackageableElementExecutable();
if (executable.id) {
executableProtocol.id = executable.id;
}
executableProtocol.title = executable.title;
executableProtocol.description = executable.description;
if (executable.executionContextKey) {
executableProtocol.executionContextKey =
executable.executionContextKey;
}
if (
executable.executable.value instanceof
ConcreteFunctionDefinition
) {
executableProtocol.executable =
new V1_PackageableElementPointer(
PackageableElementPointerType.FUNCTION,
generateFunctionPrettyName(executable.executable.value, {
fullPath: true,
spacing: false,
notIncludeParamName: true,
}),
);
} else {
executableProtocol.executable =
new V1_PackageableElementPointer(
undefined,
executable.executable.valueForSerialization ?? '',
);
}
return executableProtocol;
} else {
throw new UnsupportedOperationError(
`Can't transform data product executable`,
executable,
);
}
});
protocol.diagrams = metamodel.diagrams?.map((diagram) => {
const diagramProtocol = new V1_DataSpaceDiagram();
diagramProtocol.title = diagram.title;
diagramProtocol.description = diagram.description;
diagramProtocol.diagram = new V1_PackageableElementPointer(
undefined,
diagram.diagram.valueForSerialization ?? '',
);
return diagramProtocol;
});
if (metamodel.supportInfo) {
if (metamodel.supportInfo instanceof DataSpaceSupportEmail) {
const supportEmail = new V1_DataSpaceSupportEmail();
supportEmail.documentationUrl =
metamodel.supportInfo.documentationUrl;
supportEmail.address = metamodel.supportInfo.address;
protocol.supportInfo = supportEmail;
} else if (
metamodel.supportInfo instanceof DataSpaceSupportCombinedInfo
) {
const combinedInfo = new V1_DataSpaceSupportCombinedInfo();
combinedInfo.documentationUrl =
metamodel.supportInfo.documentationUrl;
combinedInfo.emails = metamodel.supportInfo.emails;
combinedInfo.website = metamodel.supportInfo.website;
combinedInfo.faqUrl = metamodel.supportInfo.faqUrl;
combinedInfo.supportUrl = metamodel.supportInfo.supportUrl;
protocol.supportInfo = combinedInfo;
} else {
throw new UnsupportedOperationError(
`Can't transform data product support info`,
metamodel.supportInfo,
);
}
}
return protocol;
}
return undefined;
},
];
}
V1_getExtraMappingIncludeBuilders(): V1_MappingIncludeBuilder[] {
return [
(
protocol: V1_MappingInclude,
parentMapping: Mapping,
context: V1_GraphBuilderContext,
): MappingInclude | undefined => {
if (protocol instanceof V1_MappingIncludeDataSpace) {
const dataSpace = V1_resolveDataSpace(
protocol.includedDataSpace,
context,
);
const includedMapping = new MappingIncludeDataSpace(
parentMapping,
dataSpace.value.defaultExecutionContext.mapping,
dataSpace,
);
return includedMapping;
}
return undefined;
},
];
}
V1_getExtraMappingIncludeTransformers(): V1_MappingIncludeTransformer[] {
return [
(
metamodel: MappingInclude,
context: V1_GraphTransformerContext,
): V1_MappingInclude | undefined => {
if (metamodel instanceof MappingIncludeDataSpace) {
const mappingInclude = new V1_MappingIncludeDataSpace();
mappingInclude.includedDataSpace =
metamodel.includedDataSpace.valueForSerialization ?? '';
return mappingInclude;
}
return undefined;
},
];
}
V1_getExtraMappingIncludeProtocolSerializers(): V1_MappingIncludeProtocolSerializer[] {
return [
(
protocol: V1_MappingInclude,
): PlainObject<V1_MappingInclude> | undefined => {
if (protocol instanceof V1_MappingIncludeDataSpace) {
return V1_serializeMappingInclude(protocol);
}
return undefined;
},
];
}
V1_getExtraMappingIncludeProtocolDeserializers(): V1_MappingIncludeProtocolDeserializer[] {
return [
(json: PlainObject<V1_MappingInclude>): V1_MappingInclude | undefined => {
if (json._type === V1_MAPPING_INCLUDE_DATASPACE_TYPE) {
return V1_deserializeMappingInclude(json);
}
return undefined;
},
];
}
V1_getExtraMappingIncludeIdentifierBuilders(): V1_MappingIncludeIdentifierBuilder[] {
return [
(protocol: V1_MappingInclude): string | undefined => {
if (protocol instanceof V1_MappingIncludeDataSpace) {
return protocol.includedDataSpace;
}
return undefined;
},
];
}
override V1_getExtraSavedQueryExecutionBuilder(): V1_SavedQueryExecutionBuilder[] {
return [
(
queryExec: QueryExecutionContextInfo,
entites: Entity[],
): { mapping: string; runtime: string } | undefined => {
if (queryExec instanceof QueryDataSpaceExecutionContextInfo) {
const dataSpace = queryExec.dataSpacePath;
const dataSpaceEntity = entites.find((e) => e.path === dataSpace);
if (dataSpaceEntity) {
const content = dataSpaceEntity.content;
if (content._type === V1_DATA_SPACE_ELEMENT_PROTOCOL_TYPE) {
const v1DataSpace = returnUndefOnError(() =>
V1_deserializeDataSpace(content),
);
if (v1DataSpace) {
if (v1DataSpace.executionContexts.length === 1) {
const exec = guaranteeNonNullable(
v1DataSpace.executionContexts[0],
);
return {
mapping: exec.mapping.path,
runtime: exec.defaultRuntime.path,
};
}
const resvoled =
queryExec.executionKey ?? v1DataSpace.defaultExecutionContext;
const resolvedExec = guaranteeNonNullable(
v1DataSpace.executionContexts.find(
(e) => e.name === resvoled,
),
);
return {
mapping: resolvedExec.mapping.path,
runtime: resolvedExec.defaultRuntime.path,
};
}
}
}
}
return undefined;
},
];
}
}
export const extractDataSpaceTaxonomyNodes = (
json: PlainObject<V1_DataSpace>,
): string[] => {
const ENTERPRISE_PROFILE_PATH = `meta::pure::profiles::enterprise`;
const TAXONOMY_NODES_TAG = `taxonomyNodes`;
const TAXONOMY_NODES_TAG_VALUE_DELIMITER = `,`;
const taxonomyNodes = new Set<string>();
if (json._type === V1_DATA_SPACE_ELEMENT_PROTOCOL_TYPE) {
if (Array.isArray(json.taggedValues)) {
const taggedValues = (
json.taggedValues as PlainObject<V1_TaggedValue>[]
).map((taggedValueJson) =>
deserialize(V1_taggedValueModelSchema, taggedValueJson),
);
taggedValues
.filter(
(taggedValue) =>
taggedValue.tag.profile === ENTERPRISE_PROFILE_PATH &&
taggedValue.tag.value === TAXONOMY_NODES_TAG,
)
.forEach((taggedValue) => {
taggedValue.value
.split(TAXONOMY_NODES_TAG_VALUE_DELIMITER)
.map((value) => value.trim())
.filter((value) => Boolean(value))
.forEach((value) => taxonomyNodes.add(value));
});
}
}
return Array.from(taxonomyNodes.values());
};