@finos/legend-extension-dsl-data-quality
Version:
Legend extension for Data Quality
336 lines (292 loc) • 9.51 kB
text/typescript
/**
* Copyright (c) 2026-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 { UnsupportedOperationError } from '@finos/legend-shared';
import { SUPPORTED_TYPES } from '../constants/DataQualityConstants.js';
import {
AbstractPropertyExpression,
ColSpec,
ColSpecArray,
ColSpecArrayInstance,
ColSpecInstanceValue,
Multiplicity,
observe_ColSpecArrayInstance,
observe_ColSpecInstance,
type ObserverContext,
PRIMITIVE_TYPE,
PropertyExplicitReference,
type PureModel,
type V1_ColSpec,
VariableExpression,
type AbstractProperty,
observe_AbstractPropertyExpression,
SimpleFunctionExpression,
observe_SimpleFunctionExpression,
type ValueSpecification,
observe_VariableExpression,
type CollectionInstanceValue,
type PrimitiveInstanceValue,
} from '@finos/legend-graph';
import {
buildPrimitiveCollectionInstanceValue,
buildPrimitiveInstanceValue,
} from '@finos/legend-query-builder';
export type LambdaBody = {
function?: string;
name?: string;
parameters: LambdaBody[];
_type: string;
type?: string;
value?: string | number | boolean | ColSpec | ColSpecArray;
values?: LambdaBody[];
body?: LambdaBody[];
property?: string;
};
export class ParsedFunctionExpression {
name: string;
processedParameters: (
| CollectionInstanceValue
| PrimitiveInstanceValue
| AbstractPropertyExpression
| ParsedFunctionExpression
)[];
constructor(
name: string,
processedParameters: ParsedFunctionExpression['processedParameters'] = [],
) {
this.name = name;
this.processedParameters = processedParameters;
}
}
export class DataQualityLambdaParameterParser {
static processPrimitiveParameter(
param: LambdaBody,
graph: PureModel,
observerContext: ObserverContext,
) {
const { _type, value } = param;
if (!DataQualityLambdaParameterParser.isSupportedPrimitive(_type)) {
throw new UnsupportedOperationError(
`Unsupported primitive type: ${_type}`,
);
}
const primitiveType =
DataQualityLambdaParameterParser.getPrimitiveType(_type);
return buildPrimitiveInstanceValue(
graph,
primitiveType,
value,
observerContext,
);
}
static processVariableDeclaration(param: { name?: string }) {
const { name = '' } = param;
return observe_VariableExpression(
new VariableExpression(name, Multiplicity.ZERO),
);
}
static processColSpecArray(
param: LambdaBody,
observerContext: ObserverContext,
) {
const { value } = param;
const colSpecArray = new ColSpecArray();
colSpecArray.colSpecs = (value as ColSpecArray).colSpecs.map((colSpec) => {
const colSpecValue = new ColSpec();
colSpecValue.name = colSpec.name;
return colSpecValue;
});
const colSpecArrayInstance = new ColSpecArrayInstance(
Multiplicity.ZERO_MANY,
);
colSpecArrayInstance.values = [colSpecArray];
observe_ColSpecArrayInstance(colSpecArrayInstance, observerContext);
return colSpecArrayInstance;
}
static processColSpec(param: LambdaBody, observerContext: ObserverContext) {
const { value } = param;
const colSpecValue = new ColSpec();
colSpecValue.name = (value as V1_ColSpec).name;
const colSpecInstance = new ColSpecInstanceValue(Multiplicity.ZERO_MANY);
colSpecInstance.values = [colSpecValue];
observe_ColSpecInstance(colSpecInstance, observerContext);
return colSpecInstance;
}
static processPropertyParameter(
{ property, parameters = [] }: LambdaBody,
observerContext: ObserverContext,
) {
const error = new UnsupportedOperationError(
'Property source is not supported',
);
if (parameters.length > 1) {
throw error;
}
const processedProperty = new AbstractPropertyExpression('');
processedProperty.func = PropertyExplicitReference.create({
name: property,
} as AbstractProperty);
processedProperty.parametersValues = parameters.map((parameter) => {
if (parameter._type === SUPPORTED_TYPES.VAR) {
return DataQualityLambdaParameterParser.processVariableDeclaration(
parameter,
);
}
throw error;
});
observe_AbstractPropertyExpression(processedProperty, observerContext);
return processedProperty;
}
static processCollectionParameter(
{ values = [] }: LambdaBody,
graph: PureModel,
observerContext: ObserverContext,
) {
const firstElementType = values[0]?._type ?? SUPPORTED_TYPES.STRING;
if (
!DataQualityLambdaParameterParser.isSupportedPrimitive(firstElementType)
) {
throw new UnsupportedOperationError(
`Unsupported primitive type for collection: ${firstElementType}`,
);
}
const hasMultipleTypes = values.some(
(value) => value._type !== firstElementType,
);
if (hasMultipleTypes) {
throw new UnsupportedOperationError(
'Multi-type collections are not supported. All elements must be of the same type.',
);
}
return buildPrimitiveCollectionInstanceValue(
graph,
this.getPrimitiveType(firstElementType),
values.map((value) => value.value),
observerContext,
);
}
static processFunctionParameter(
{ parameters = [], function: name = '' }: LambdaBody,
graph: PureModel,
observerContext: ObserverContext,
): ParsedFunctionExpression {
const processedParameters = parameters.map((parameter) => {
if (
DataQualityLambdaParameterParser.isSupportedPrimitive(parameter._type)
) {
return DataQualityLambdaParameterParser.processPrimitiveParameter(
parameter,
graph,
observerContext,
);
} else {
switch (parameter._type) {
case SUPPORTED_TYPES.PROPERTY:
return DataQualityLambdaParameterParser.processPropertyParameter(
parameter,
observerContext,
);
case SUPPORTED_TYPES.COLLECTION:
return DataQualityLambdaParameterParser.processCollectionParameter(
parameter,
graph,
observerContext,
);
case SUPPORTED_TYPES.FUNCTION:
return DataQualityLambdaParameterParser.processFunctionParameter(
parameter,
graph,
observerContext,
);
default:
throw new UnsupportedOperationError(
`Cannot process type: ${parameter._type}`,
);
}
}
});
return new ParsedFunctionExpression(name, processedParameters);
}
static createSimpleFunctionExpression(
name: string,
parameters: ValueSpecification[],
observerContext: ObserverContext,
) {
const processedFun = new SimpleFunctionExpression(name);
processedFun.parametersValues = parameters;
observe_SimpleFunctionExpression(processedFun, observerContext);
return processedFun;
}
static isSupportedPrimitive(type: string) {
switch (type) {
case SUPPORTED_TYPES.NUMBER:
case SUPPORTED_TYPES.STRING:
case SUPPORTED_TYPES.FLOAT:
case SUPPORTED_TYPES.DECIMAL:
case SUPPORTED_TYPES.INTEGER:
return true;
default:
return false;
}
}
static getPrimitiveType(type: string): PRIMITIVE_TYPE {
switch (type) {
case SUPPORTED_TYPES.STRING:
return PRIMITIVE_TYPE.STRING;
case SUPPORTED_TYPES.NUMBER:
return PRIMITIVE_TYPE.NUMBER;
case SUPPORTED_TYPES.INTEGER:
return PRIMITIVE_TYPE.INTEGER;
case SUPPORTED_TYPES.FLOAT:
return PRIMITIVE_TYPE.FLOAT;
case SUPPORTED_TYPES.DECIMAL:
return PRIMITIVE_TYPE.DECIMAL;
default:
throw new UnsupportedOperationError(
`Unsupported primitive type: ${type}`,
);
}
}
static validateLambdaParameter({ parameters = [], body = [] }: LambdaBody) {
if (parameters.length === 0) {
throw new UnsupportedOperationError('Lambda parameters cannot be empty');
}
if (parameters.length > 1) {
throw new UnsupportedOperationError(
'Only single function in lambda parameters is supported',
);
}
const firstParameter = parameters[0];
if (firstParameter?._type !== SUPPORTED_TYPES.VAR) {
throw new UnsupportedOperationError(
`Unsupported parameter type in lambda parameter: ${firstParameter?._type}`,
);
}
if (body.length === 0) {
throw new UnsupportedOperationError('Lambda body cannot be empty');
}
if (body.length > 1) {
throw new UnsupportedOperationError(
'Only single function in lambda body is supported',
);
}
const functionExpression = body[0];
if (functionExpression?._type !== SUPPORTED_TYPES.FUNCTION) {
throw new UnsupportedOperationError(
`Unsupported expression type in lambda body: ${functionExpression?._type}`,
);
}
}
}