Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@sap-ux/annotation-converter

Package Overview
Dependencies
Maintainers
3
Versions
76
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@sap-ux/annotation-converter - npm Package Compare versions

Comparing version 0.5.23 to 0.6.0

6

CHANGELOG.md
# @sap-ux/annotation-converter
## 0.6.0
### Minor Changes
- 956ee23: The annotation converter now delays the conversion of certain elements and annotations until the converted results are requested
## 0.5.23

@@ -4,0 +10,0 @@

1712

dist/converter.js

@@ -6,351 +6,270 @@ "use strict";

/**
* Symbol to extend an annotation with the reference to its target.
*/
const ANNOTATION_TARGET = Symbol('Annotation Target');
/**
* Append an object to the list of visited objects if it is different from the last object in the list.
*
* @param objectPath The list of visited objects
* @param visitedObject The object
* @returns The list of visited objects
*/
class Path {
/**
* @param pathExpression
* @param targetName
* @param annotationsTerm
* @param term
*/
constructor(pathExpression, targetName, annotationsTerm, term) {
this.path = pathExpression.Path;
this.type = 'Path';
this.$target = targetName;
this.term = term;
this.annotationsTerm = annotationsTerm;
function appendObjectPath(objectPath, visitedObject) {
if (objectPath[objectPath.length - 1] !== visitedObject) {
objectPath.push(visitedObject);
}
return objectPath;
}
/**
* Creates a Map based on the fullyQualifiedName of each object part of the metadata.
* Resolves a (possibly relative) path.
*
* @param rawMetadata the rawMetadata we're working against
* @returns the objectmap for easy access to the different object of the metadata
* @param converter Converter
* @param startElement The starting point in case of relative path resolution
* @param path The path to resolve
* @param annotationsTerm Only for error reporting: The annotation term
* @returns An object containing the resolved target and the elements that were visited while getting to the target.
*/
function buildObjectMap(rawMetadata) {
var _a;
const objectMap = {};
if ((_a = rawMetadata.schema.entityContainer) === null || _a === void 0 ? void 0 : _a.fullyQualifiedName) {
objectMap[rawMetadata.schema.entityContainer.fullyQualifiedName] = rawMetadata.schema.entityContainer;
function resolveTarget(converter, startElement, path, annotationsTerm) {
// absolute paths always start at the entity container
if (path.startsWith('/')) {
path = path.substring(1);
startElement = undefined; // will resolve to the entity container (see below)
}
for (const entitySet of rawMetadata.schema.entitySets) {
objectMap[entitySet.fullyQualifiedName] = entitySet;
}
for (const singleton of rawMetadata.schema.singletons) {
objectMap[singleton.fullyQualifiedName] = singleton;
}
for (const action of rawMetadata.schema.actions) {
objectMap[action.fullyQualifiedName] = action;
if (action.isBound) {
const [actionName, actionBinding] = (0, utils_1.splitAtFirst)(action.fullyQualifiedName, '(');
if (!objectMap[actionName]) {
objectMap[actionName] = {
_type: 'UnboundGenericAction',
actions: []
};
}
objectMap[actionName].actions.push(action);
const type = (0, utils_1.substringBeforeFirst)(actionBinding, ')');
objectMap[`${type}/${actionName}`] = action;
const pathSegments = path.split('/').reduce((targetPath, segment) => {
if (segment.includes('@')) {
// Separate out the annotation
const [pathPart, annotationPart] = (0, utils_1.splitAtFirst)(segment, '@');
targetPath.push(pathPart);
targetPath.push(`@${annotationPart}`);
}
else if (!action.fullyQualifiedName.includes('()')) {
// unbound action - add empty parentheses at the end
objectMap[`${action.fullyQualifiedName}()`] = action;
else {
targetPath.push(segment);
}
for (const parameter of action.parameters) {
objectMap[parameter.fullyQualifiedName] = parameter;
}
return targetPath;
}, []);
// determine the starting point for the resolution
if (startElement === undefined) {
// no starting point given: start at the entity container
startElement = converter.getConvertedEntityContainer();
}
for (const actionImport of rawMetadata.schema.actionImports) {
objectMap[actionImport.fullyQualifiedName] = actionImport;
else if (startElement[ANNOTATION_TARGET] !== undefined) {
// annotation: start at the annotation target
startElement = startElement[ANNOTATION_TARGET];
}
for (const complexType of rawMetadata.schema.complexTypes) {
objectMap[complexType.fullyQualifiedName] = complexType;
for (const property of complexType.properties) {
objectMap[property.fullyQualifiedName] = property;
}
else if (startElement._type === 'Property') {
// property: start at the entity type the property belongs to
startElement = converter.getConvertedEntityType((0, utils_1.substringBeforeFirst)(startElement.fullyQualifiedName, '/'));
}
for (const typeDefinition of rawMetadata.schema.typeDefinitions) {
objectMap[typeDefinition.fullyQualifiedName] = typeDefinition;
}
for (const entityType of rawMetadata.schema.entityTypes) {
entityType.annotations = {}; // 'annotations' property is mandatory
objectMap[entityType.fullyQualifiedName] = entityType;
objectMap[`Collection(${entityType.fullyQualifiedName})`] = entityType;
for (const property of entityType.entityProperties) {
objectMap[property.fullyQualifiedName] = property;
// Handle complex types
const complexTypeDefinition = objectMap[property.type];
if ((0, utils_1.isComplexTypeDefinition)(complexTypeDefinition)) {
for (const complexTypeProp of complexTypeDefinition.properties) {
const complexTypePropTarget = Object.assign(complexTypeProp, {
_type: 'Property',
fullyQualifiedName: property.fullyQualifiedName + '/' + complexTypeProp.name
});
objectMap[complexTypePropTarget.fullyQualifiedName] = complexTypePropTarget;
}
}
const result = pathSegments.reduce((current, segment) => {
var _a, _b, _c, _d, _e;
const error = (message) => {
current.messages.push({ message });
current.objectPath = appendObjectPath(current.objectPath, undefined);
current.target = undefined;
return current;
};
if (current.target === undefined) {
return current;
}
for (const navProperty of entityType.navigationProperties) {
objectMap[navProperty.fullyQualifiedName] = navProperty;
}
}
for (const annotationSource of Object.keys(rawMetadata.schema.annotations)) {
for (const annotationList of rawMetadata.schema.annotations[annotationSource]) {
const currentTargetName = (0, utils_1.unalias)(rawMetadata.references, annotationList.target);
annotationList.annotations.forEach((annotation) => {
let annotationFQN = `${currentTargetName}@${(0, utils_1.unalias)(rawMetadata.references, annotation.term)}`;
if (annotation.qualifier) {
annotationFQN += `#${annotation.qualifier}`;
}
objectMap[annotationFQN] = annotation;
annotation.fullyQualifiedName = annotationFQN;
});
}
}
return objectMap;
}
/**
* Combine two strings representing path in the metamodel while ensuring their specificities (annotation...) are respected.
*
* @param currentTarget the current path
* @param path the part we want to append
* @returns the complete path including the extension.
*/
function combinePath(currentTarget, path) {
if (path.startsWith('@')) {
return currentTarget + (0, utils_1.unalias)(utils_1.defaultReferences, path);
}
else {
return currentTarget + '/' + path;
}
}
const ALL_ANNOTATION_ERRORS = {};
let ANNOTATION_ERRORS = [];
/**
* @param path
* @param oErrorMsg
*/
function addAnnotationErrorMessage(path, oErrorMsg) {
if (!ALL_ANNOTATION_ERRORS[path]) {
ALL_ANNOTATION_ERRORS[path] = [oErrorMsg];
}
else {
ALL_ANNOTATION_ERRORS[path].push(oErrorMsg);
}
}
/**
* Resolves a specific path based on the objectMap.
*
* @param objectMap
* @param currentTarget
* @param path
* @param pathOnly
* @param includeVisitedObjects
* @param annotationsTerm
* @returns the resolved object
*/
function _resolveTarget(objectMap, currentTarget, path, pathOnly = false, includeVisitedObjects = false, annotationsTerm) {
let oErrorMsg;
if (!path) {
return undefined;
}
const aVisitedObjects = [];
if (currentTarget && currentTarget._type === 'Property') {
currentTarget = objectMap[(0, utils_1.substringBeforeFirst)(currentTarget.fullyQualifiedName, '/')];
}
path = combinePath(currentTarget.fullyQualifiedName, path);
const pathSplit = path.split('/');
const targetPathSplit = [];
pathSplit.forEach((pathPart) => {
// Separate out the annotation
if (pathPart.includes('@')) {
const [splittedPath, annotationPath] = (0, utils_1.splitAtFirst)(pathPart, '@');
targetPathSplit.push(splittedPath);
targetPathSplit.push(`@${annotationPath}`);
}
else {
targetPathSplit.push(pathPart);
}
});
let currentPath = path;
let currentContext = currentTarget;
const target = targetPathSplit.reduce((currentValue, pathPart) => {
if (pathPart === '$Type' && currentValue._type === 'EntityType') {
return currentValue;
}
if (pathPart === '$' && currentValue._type === 'EntitySet') {
return currentValue;
}
if ((pathPart === '@$ui5.overload' || pathPart === '0') && currentValue._type === 'Action') {
return currentValue;
}
if (pathPart.length === 0) {
// Empty Path after an entitySet means entityType
if (currentValue &&
(currentValue._type === 'EntitySet' || currentValue._type === 'Singleton') &&
currentValue.entityType) {
if (includeVisitedObjects) {
aVisitedObjects.push(currentValue);
}
currentValue = currentValue.entityType;
current.objectPath = appendObjectPath(current.objectPath, current.target);
// Annotation
if (segment.startsWith('@') && segment !== '@$ui5.overload') {
const [vocabularyAlias, term] = converter.splitTerm(segment);
const annotation = (_a = current.target.annotations[vocabularyAlias.substring(1)]) === null || _a === void 0 ? void 0 : _a[term];
if (annotation !== undefined) {
current.target = annotation;
return current;
}
if (currentValue && currentValue._type === 'NavigationProperty' && currentValue.targetType) {
if (includeVisitedObjects) {
aVisitedObjects.push(currentValue);
}
currentValue = currentValue.targetType;
}
return currentValue;
return error(`Annotation '${segment.substring(1)}' not found on ${current.target._type} '${current.target.fullyQualifiedName}'`);
}
if (includeVisitedObjects && currentValue !== null && currentValue !== undefined) {
aVisitedObjects.push(currentValue);
}
if (!currentValue) {
currentPath = pathPart;
}
else if ((currentValue._type === 'EntitySet' || currentValue._type === 'Singleton') && pathPart === '$Type') {
currentValue = currentValue.targetType;
return currentValue;
}
else if ((currentValue._type === 'EntitySet' || currentValue._type === 'Singleton') &&
pathPart === '$NavigationPropertyBinding') {
currentValue = currentValue.navigationPropertyBinding;
return currentValue;
}
else if ((currentValue._type === 'EntitySet' || currentValue._type === 'Singleton') &&
currentValue.entityType) {
currentPath = combinePath(currentValue.entityTypeName, pathPart);
}
else if (currentValue._type === 'NavigationProperty') {
currentPath = combinePath(currentValue.fullyQualifiedName, pathPart);
if (!objectMap[currentPath]) {
// Fallback log error
currentPath = combinePath(currentValue.targetTypeName, pathPart);
// $Path / $AnnotationPath syntax
if (current.target.$target) {
let subPath;
if (segment === '$AnnotationPath') {
subPath = current.target.value;
}
}
else if (currentValue._type === 'Property') {
// ComplexType or Property
if (currentValue.targetType) {
currentPath = combinePath(currentValue.targetType.fullyQualifiedName, pathPart);
else if (segment === '$Path') {
subPath = current.target.path;
}
else {
currentPath = combinePath(currentValue.fullyQualifiedName, pathPart);
if (subPath !== undefined) {
const subTarget = resolveTarget(converter, current.target[ANNOTATION_TARGET], subPath);
subTarget.objectPath.forEach((visitedSubObject) => {
if (!current.objectPath.includes(visitedSubObject)) {
current.objectPath = appendObjectPath(current.objectPath, visitedSubObject);
}
});
current.target = subTarget.target;
current.objectPath = appendObjectPath(current.objectPath, current.target);
return current;
}
}
else if (currentValue._type === 'Action' && currentValue.isBound) {
currentPath = combinePath(currentValue.fullyQualifiedName, pathPart);
if (pathPart === '$Parameter') {
return currentValue.parameters;
// traverse based on the element type
switch ((_b = current.target) === null || _b === void 0 ? void 0 : _b._type) {
case 'EntityContainer':
{
const thisElement = current.target;
if (segment === '' || segment === thisElement.fullyQualifiedName) {
return current;
}
// next element: EntitySet, Singleton or ActionImport?
const nextElement = (_d = (_c = thisElement.entitySets.by_name(segment)) !== null && _c !== void 0 ? _c : thisElement.singletons.by_name(segment)) !== null && _d !== void 0 ? _d : thisElement.actionImports.by_name(segment);
if (nextElement) {
current.target = nextElement;
return current;
}
}
break;
case 'EntitySet':
case 'Singleton': {
const thisElement = current.target;
if (segment === '' || segment === '$Type') {
// Empty Path after an EntitySet or Singleton means EntityType
current.target = thisElement.entityType;
return current;
}
if (segment === '$') {
return current;
}
if (segment === '$NavigationPropertyBinding') {
const navigationPropertyBindings = thisElement.navigationPropertyBinding;
current.target = navigationPropertyBindings;
return current;
}
// continue resolving at the EntitySet's or Singleton's type
const result = resolveTarget(converter, thisElement.entityType, segment);
current.target = result.target;
current.objectPath = result.objectPath.reduce(appendObjectPath, current.objectPath);
return current;
}
if (!objectMap[currentPath]) {
currentPath = combinePath(currentValue.sourceType, pathPart);
}
}
else if (currentValue._type === 'ActionParameter') {
currentPath = combinePath((0, utils_1.substringBeforeLast)(currentTarget.fullyQualifiedName, '/'), pathPart);
if (!objectMap[currentPath]) {
currentPath = combinePath(objectMap[(0, utils_1.substringBeforeLast)(currentTarget.fullyQualifiedName, '/')].sourceType, pathPart);
}
}
else {
currentPath = combinePath(currentValue.fullyQualifiedName, pathPart);
if (pathPart !== 'name' && currentValue[pathPart] !== undefined) {
return currentValue[pathPart];
}
else if (pathPart === '$AnnotationPath' && currentValue.$target) {
const contextToResolve = objectMap[(0, utils_1.substringBeforeFirst)(currentValue.fullyQualifiedName, '@')];
const subTarget = _resolveTarget(objectMap, contextToResolve, currentValue.value, false, true);
subTarget.visitedObjects.forEach((visitedSubObject) => {
if (!aVisitedObjects.includes(visitedSubObject)) {
aVisitedObjects.push(visitedSubObject);
case 'EntityType':
{
const thisElement = current.target;
if (segment === '' || segment === '$Type') {
return current;
}
});
return subTarget.target;
const property = thisElement.entityProperties.by_name(segment);
if (property) {
current.target = property;
return current;
}
const navigationProperty = thisElement.navigationProperties.by_name(segment);
if (navigationProperty) {
current.target = navigationProperty;
return current;
}
const action = thisElement.actions[segment];
if (action) {
current.target = action;
return current;
}
}
break;
case 'ActionImport': {
// continue resolving at the Action
const result = resolveTarget(converter, current.target.action, segment);
current.target = result.target;
current.objectPath = result.objectPath.reduce(appendObjectPath, current.objectPath);
return current;
}
else if (pathPart === '$Path' && currentValue.$target) {
currentContext = aVisitedObjects
.concat()
.reverse()
.find((obj) => obj._type === 'EntityType' ||
obj._type === 'EntitySet' ||
obj._type === 'Singleton' ||
obj._type === 'NavigationProperty');
if (currentContext) {
const subTarget = _resolveTarget(objectMap, currentContext, currentValue.path, false, true);
subTarget.visitedObjects.forEach((visitedSubObject) => {
if (!aVisitedObjects.includes(visitedSubObject)) {
aVisitedObjects.push(visitedSubObject);
}
});
return subTarget.target;
case 'Action': {
const thisElement = current.target;
if (segment === '') {
return current;
}
return currentValue.$target;
if (segment === '@$ui5.overload' || segment === '0') {
return current;
}
if (segment === '$Parameter' && thisElement.isBound) {
current.target = thisElement.parameters;
return current;
}
const nextElement = (_e = thisElement.parameters[segment]) !== null && _e !== void 0 ? _e : thisElement.parameters.find((param) => param.name === segment);
if (nextElement) {
current.target = nextElement;
return current;
}
break;
}
else if (pathPart.startsWith('$Path') && currentValue.$target) {
const intermediateTarget = currentValue.$target;
currentPath = combinePath(intermediateTarget.fullyQualifiedName, pathPart.substring(5));
}
else if (currentValue.hasOwnProperty('$Type') && !objectMap[currentPath]) {
// This is now an annotation value
const entityType = objectMap[(0, utils_1.substringBeforeFirst)(currentValue.fullyQualifiedName, '@')];
if (entityType) {
currentPath = combinePath(entityType.fullyQualifiedName, pathPart);
case 'Property':
{
const thisElement = current.target;
// Property or NavigationProperty of the ComplexType
const type = thisElement.targetType;
if (type !== undefined) {
const property = type.properties.by_name(segment);
if (property) {
current.target = property;
return current;
}
const navigationProperty = type.navigationProperties.by_name(segment);
if (navigationProperty) {
current.target = navigationProperty;
return current;
}
}
}
}
break;
case 'ActionParameter':
const referencedType = current.target.typeReference;
if (referencedType !== undefined) {
const result = resolveTarget(converter, referencedType, segment);
current.target = result.target;
current.objectPath = result.objectPath.reduce(appendObjectPath, current.objectPath);
return current;
}
break;
case 'NavigationProperty':
// continue at the NavigationProperty's target type
const result = resolveTarget(converter, current.target.targetType, segment);
current.target = result.target;
current.objectPath = result.objectPath.reduce(appendObjectPath, current.objectPath);
return current;
default:
if (current.target[segment]) {
current.target = current.target[segment];
current.objectPath = appendObjectPath(current.objectPath, current.target);
}
return current;
}
return objectMap[currentPath];
}, null);
if (!target) {
return error(`Element '${segment}' not found at ${current.target._type} '${current.target.fullyQualifiedName}'`);
}, { target: startElement, objectPath: [], messages: [] });
// Diagnostics
result.messages.forEach((message) => converter.logError(message.message));
if (!result.target) {
if (annotationsTerm) {
const annotationType = inferTypeFromTerm(annotationsTerm, currentTarget);
oErrorMsg = {
message: 'Unable to resolve the path expression: ' +
'\n' +
path +
'\n' +
'\n' +
'Hint: Check and correct the path values under the following structure in the metadata (annotation.xml file or CDS annotations for the application): \n\n' +
'<Annotation Term = ' +
annotationsTerm +
'>' +
'\n' +
'<Record Type = ' +
annotationType +
'>' +
'\n' +
'<AnnotationPath = ' +
path +
'>'
};
addAnnotationErrorMessage(path, oErrorMsg);
const annotationType = inferTypeFromTerm(converter, annotationsTerm, startElement.fullyQualifiedName);
converter.logError('Unable to resolve the path expression: ' +
'\n' +
path +
'\n' +
'\n' +
'Hint: Check and correct the path values under the following structure in the metadata (annotation.xml file or CDS annotations for the application): \n\n' +
'<Annotation Term = ' +
annotationsTerm +
'>' +
'\n' +
'<Record Type = ' +
annotationType +
'>' +
'\n' +
'<AnnotationPath = ' +
path +
'>');
}
else {
oErrorMsg = {
message: 'Unable to resolve the path expression: ' +
path +
'\n' +
'\n' +
'Hint: Check and correct the path values under the following structure in the metadata (annotation.xml file or CDS annotations for the application): \n\n' +
'<Annotation Term = ' +
pathSplit[0] +
'>' +
'\n' +
'<PropertyValue Path= ' +
pathSplit[1] +
'>'
};
addAnnotationErrorMessage(path, oErrorMsg);
converter.logError('Unable to resolve the path expression: ' +
path +
'\n' +
'\n' +
'Hint: Check and correct the path values under the following structure in the metadata (annotation.xml file or CDS annotations for the application): \n\n' +
'<Annotation Term = ' +
pathSegments[0] +
'>' +
'\n' +
'<PropertyValue Path= ' +
pathSegments[1] +
'>');
}
}
if (pathOnly) {
return currentPath;
}
if (includeVisitedObjects) {
return {
visitedObjects: aVisitedObjects,
target: target
};
}
return target;
return result;
}

@@ -366,3 +285,3 @@ /**

}
function parseValue(propertyValue, valueFQN, objectMap, context) {
function parseValue(converter, currentTarget, currentTerm, currentProperty, currentSource, propertyValue, valueFQN) {
if (propertyValue === undefined) {

@@ -383,3 +302,3 @@ return undefined;

case 'EnumMember':
const aliasedEnum = (0, utils_1.alias)(context.rawMetadata.references, propertyValue.EnumMember);
const aliasedEnum = converter.alias(propertyValue.EnumMember);
const splitEnum = aliasedEnum.split(' ');

@@ -395,3 +314,4 @@ if (splitEnum[0] && utils_1.EnumIsFlag[(0, utils_1.substringBeforeFirst)(splitEnum[0], '/')]) {

fullyQualifiedName: valueFQN,
$target: _resolveTarget(objectMap, context.currentTarget, propertyValue.PropertyPath, false, false, context.currentTerm)
$target: resolveTarget(converter, currentTarget, propertyValue.PropertyPath, currentTerm).target,
[ANNOTATION_TARGET]: currentTarget
};

@@ -403,29 +323,36 @@ case 'NavigationPropertyPath':

fullyQualifiedName: valueFQN,
$target: _resolveTarget(objectMap, context.currentTarget, propertyValue.NavigationPropertyPath, false, false, context.currentTerm)
$target: resolveTarget(converter, currentTarget, propertyValue.NavigationPropertyPath, currentTerm)
.target,
[ANNOTATION_TARGET]: currentTarget
};
case 'AnnotationPath':
const annotationTarget = _resolveTarget(objectMap, context.currentTarget, (0, utils_1.unalias)(context.rawMetadata.references, propertyValue.AnnotationPath), true, false, context.currentTerm);
const annotationPath = {
return {
type: 'AnnotationPath',
value: propertyValue.AnnotationPath,
fullyQualifiedName: valueFQN,
$target: annotationTarget,
annotationsTerm: context.currentTerm,
$target: resolveTarget(converter, currentTarget, converter.unalias(propertyValue.AnnotationPath), currentTerm).target,
annotationsTerm: currentTerm,
term: '',
path: ''
path: '',
[ANNOTATION_TARGET]: currentTarget
};
context.unresolvedAnnotations.push({ inline: false, toResolve: annotationPath });
return annotationPath;
case 'Path':
const $target = _resolveTarget(objectMap, context.currentTarget, propertyValue.Path, true, false, context.currentTerm);
const path = new Path(propertyValue, $target, context.currentTerm, '');
context.unresolvedAnnotations.push({
inline: isAnnotationPath(propertyValue.Path),
toResolve: path
});
return path;
const $target = resolveTarget(converter, currentTarget, propertyValue.Path, currentTerm).target;
if (isAnnotationPath(propertyValue.Path)) {
// inline the target
return $target;
}
else {
return {
type: 'Path',
path: propertyValue.Path,
fullyQualifiedName: valueFQN,
$target: $target,
[ANNOTATION_TARGET]: currentTarget
};
}
case 'Record':
return parseRecord(propertyValue.Record, valueFQN, objectMap, context);
return parseRecord(converter, currentTerm, currentTarget, currentProperty, currentSource, propertyValue.Record, valueFQN);
case 'Collection':
return parseCollection(propertyValue.Collection, valueFQN, objectMap, context);
return parseCollection(converter, currentTarget, currentTerm, currentProperty, currentSource, propertyValue.Collection, valueFQN);
case 'Apply':

@@ -450,8 +377,9 @@ case 'Null':

*
* @param annotationsTerm The annotation term
* @param annotationTarget the annotation target
* @param currentProperty the current property of the record
* @returns the inferred type.
* @param converter Converter
* @param annotationsTerm The annotation term
* @param annotationTarget The annotation target
* @param currentProperty The current property of the record
* @returns The inferred type.
*/
function inferTypeFromTerm(annotationsTerm, annotationTarget, currentProperty) {
function inferTypeFromTerm(converter, annotationsTerm, annotationTarget, currentProperty) {
let targetType = utils_1.TermToTypes[annotationsTerm];

@@ -462,5 +390,3 @@ if (currentProperty) {

}
const oErrorMsg = {
isError: false,
message: `The type of the record used within the term ${annotationsTerm} was not defined and was inferred as ${targetType}.
converter.logError(`The type of the record used within the term ${annotationsTerm} was not defined and was inferred as ${targetType}.
Hint: If possible, try to maintain the Type property for each Record.

@@ -471,79 +397,74 @@ <Annotations Target="${annotationTarget}">

</Annotation>
</Annotations>`
};
addAnnotationErrorMessage(annotationTarget + '/' + annotationsTerm, oErrorMsg);
</Annotations>`);
return targetType;
}
function isDataFieldWithForAction(annotationContent, annotationTerm) {
function isDataFieldWithForAction(annotationContent) {
return (annotationContent.hasOwnProperty('Action') &&
(annotationTerm.$Type === 'com.sap.vocabularies.UI.v1.DataFieldForAction' ||
annotationTerm.$Type === 'com.sap.vocabularies.UI.v1.DataFieldWithAction'));
(annotationContent.$Type === 'com.sap.vocabularies.UI.v1.DataFieldForAction' ||
annotationContent.$Type === 'com.sap.vocabularies.UI.v1.DataFieldWithAction'));
}
function parseRecordType(recordDefinition, context) {
function parseRecordType(converter, currentTerm, currentTarget, currentProperty, recordDefinition) {
let targetType;
if (!recordDefinition.type && context.currentTerm) {
targetType = inferTypeFromTerm(context.currentTerm, context.currentTarget.fullyQualifiedName, context.currentProperty);
if (!recordDefinition.type && currentTerm) {
targetType = inferTypeFromTerm(converter, currentTerm, currentTarget.fullyQualifiedName, currentProperty);
}
else {
targetType = (0, utils_1.unalias)(context.rawMetadata.references, recordDefinition.type);
targetType = converter.unalias(recordDefinition.type);
}
return targetType;
}
function parseRecord(recordDefinition, currentFQN, objectMap, context) {
const targetType = parseRecordType(recordDefinition, context);
function parseRecord(converter, currentTerm, currentTarget, currentProperty, currentSource, annotationRecord, currentFQN) {
var _a;
const annotationTerm = {
$Type: targetType,
$Type: parseRecordType(converter, currentTerm, currentTarget, currentProperty, annotationRecord),
fullyQualifiedName: currentFQN,
annotations: {}
[ANNOTATION_TARGET]: currentTarget
};
const annotationContent = {};
if (Array.isArray(recordDefinition.annotations)) {
const subAnnotationList = {
target: currentFQN,
annotations: recordDefinition.annotations,
__source: context.currentSource
};
context.additionalAnnotations.push(subAnnotationList);
}
if (recordDefinition.propertyValues) {
recordDefinition.propertyValues.forEach((propertyValue) => {
var _a;
context.currentProperty = propertyValue.name;
annotationContent[propertyValue.name] = parseValue(propertyValue.value, `${currentFQN}/${propertyValue.name}`, objectMap, context);
if (Array.isArray(propertyValue.annotations)) {
const subAnnotationList = {
target: `${currentFQN}/${propertyValue.name}`,
annotations: propertyValue.annotations,
__source: context.currentSource
};
context.additionalAnnotations.push(subAnnotationList);
// annotations on the record
(0, utils_1.lazy)(annotationTerm, 'annotations', () => {
var _a;
// be graceful when resolving annotations on annotations: Sometimes they are referenced directly, sometimes they
// are part of the global annotations list
let annotations;
if (annotationRecord.annotations && annotationRecord.annotations.length > 0) {
annotations = annotationRecord.annotations;
}
else {
annotations = (_a = converter.rawAnnotationsPerTarget[currentFQN]) === null || _a === void 0 ? void 0 : _a.annotations;
}
annotations === null || annotations === void 0 ? void 0 : annotations.forEach((annotation) => {
annotation.target = currentFQN;
annotation.__source = currentSource;
annotation[ANNOTATION_TARGET] = currentTarget;
annotation.fullyQualifiedName = `${currentFQN}@${annotation.term}`;
});
return createAnnotationsObject(converter, annotationTerm, annotations !== null && annotations !== void 0 ? annotations : []);
});
const annotationContent = (_a = annotationRecord.propertyValues) === null || _a === void 0 ? void 0 : _a.reduce((annotationContent, propertyValue) => {
(0, utils_1.lazy)(annotationContent, propertyValue.name, () => parseValue(converter, currentTarget, currentTerm, propertyValue.name, currentSource, propertyValue.value, `${currentFQN}/${propertyValue.name}`));
return annotationContent;
}, annotationTerm);
if (isDataFieldWithForAction(annotationContent)) {
(0, utils_1.lazy)(annotationContent, 'ActionTarget', () => {
var _a, _b;
// try to resolve to a bound action of the annotation target
let actionTarget = (_a = currentTarget.actions) === null || _a === void 0 ? void 0 : _a[annotationContent.Action];
if (!actionTarget) {
// try to find a corresponding unbound action
actionTarget = (_b = converter.getConvertedActionImport(annotationContent.Action)) === null || _b === void 0 ? void 0 : _b.action;
}
if (isDataFieldWithForAction(annotationContent, annotationTerm)) {
// try to resolve to a bound action of the annotation target
annotationContent.ActionTarget = (_a = context.currentTarget.actions) === null || _a === void 0 ? void 0 : _a[annotationContent.Action];
if (!annotationContent.ActionTarget) {
const action = objectMap[annotationContent.Action];
if (action === null || action === void 0 ? void 0 : action.isBound) {
// bound action of a different entity type
annotationContent.ActionTarget = action;
}
else if (action) {
// unbound action --> resolve via the action import
annotationContent.ActionTarget = action.action;
}
if (!actionTarget) {
// try to find a corresponding bound (!) action
actionTarget = converter.getConvertedAction(annotationContent.Action);
if (!(actionTarget === null || actionTarget === void 0 ? void 0 : actionTarget.isBound)) {
actionTarget = undefined;
}
if (!annotationContent.ActionTarget) {
// Add to diagnostics debugger;
ANNOTATION_ERRORS.push({
message: 'Unable to resolve the action ' +
annotationContent.Action +
' defined for ' +
annotationTerm.fullyQualifiedName
});
}
}
if (!actionTarget) {
converter.logError(`Unable to resolve the action '${annotationContent.Action}' defined for '${annotationTerm.fullyQualifiedName}'`);
}
return actionTarget;
});
context.currentProperty = undefined;
}
return Object.assign(annotationTerm, annotationContent);
return annotationContent;
}

@@ -585,3 +506,3 @@ /**

}
function parseCollection(collectionDefinition, parentFQN, objectMap, context) {
function parseCollection(converter, currentTarget, currentTerm, currentProperty, currentSource, collectionDefinition, parentFQN) {
const collectionDefinitionType = getOrInferCollectionType(collectionDefinition);

@@ -591,49 +512,47 @@ switch (collectionDefinitionType) {

return collectionDefinition.map((propertyPath, propertyIdx) => {
return {
const result = {
type: 'PropertyPath',
value: propertyPath.PropertyPath,
fullyQualifiedName: `${parentFQN}/${propertyIdx}`,
$target: _resolveTarget(objectMap, context.currentTarget, propertyPath.PropertyPath, false, false, context.currentTerm)
fullyQualifiedName: `${parentFQN}/${propertyIdx}`
};
(0, utils_1.lazy)(result, '$target', () => {
var _a;
return (_a = resolveTarget(converter, currentTarget, propertyPath.PropertyPath, currentTerm)
.target) !== null && _a !== void 0 ? _a : {};
} // TODO: $target is mandatory - throw an error?
);
return result;
});
case 'Path':
// TODO: make lazy?
return collectionDefinition.map((pathValue) => {
const $target = _resolveTarget(objectMap, context.currentTarget, pathValue.Path, true, false, context.currentTerm);
const path = new Path(pathValue, $target, context.currentTerm, '');
context.unresolvedAnnotations.push({
inline: isAnnotationPath(pathValue.Path),
toResolve: path
});
return path;
return resolveTarget(converter, currentTarget, pathValue.Path, currentTerm).target;
});
case 'AnnotationPath':
return collectionDefinition.map((annotationPath, annotationIdx) => {
const annotationTarget = _resolveTarget(objectMap, context.currentTarget, annotationPath.AnnotationPath, true, false, context.currentTerm);
const annotationCollectionElement = {
const result = {
type: 'AnnotationPath',
value: annotationPath.AnnotationPath,
fullyQualifiedName: `${parentFQN}/${annotationIdx}`,
$target: annotationTarget,
annotationsTerm: context.currentTerm,
annotationsTerm: currentTerm,
term: '',
path: ''
};
context.unresolvedAnnotations.push({
inline: false,
toResolve: annotationCollectionElement
});
return annotationCollectionElement;
(0, utils_1.lazy)(result, '$target', () => resolveTarget(converter, currentTarget, annotationPath.AnnotationPath, currentTerm).target);
return result;
});
case 'NavigationPropertyPath':
return collectionDefinition.map((navPropertyPath, navPropIdx) => {
return {
const result = {
type: 'NavigationPropertyPath',
value: navPropertyPath.NavigationPropertyPath,
fullyQualifiedName: `${parentFQN}/${navPropIdx}`,
$target: _resolveTarget(objectMap, context.currentTarget, navPropertyPath.NavigationPropertyPath, false, false, context.currentTerm)
fullyQualifiedName: `${parentFQN}/${navPropIdx}`
};
(0, utils_1.lazy)(result, '$target', () => resolveTarget(converter, currentTarget, navPropertyPath.NavigationPropertyPath, currentTerm)
.target);
return result;
});
case 'Record':
return collectionDefinition.map((recordDefinition, recordIdx) => {
return parseRecord(recordDefinition, `${parentFQN}/${recordIdx}`, objectMap, context);
return parseRecord(converter, currentTerm, currentTarget, currentProperty, currentSource, recordDefinition, `${parentFQN}/${recordIdx}`);
});

@@ -652,13 +571,8 @@ case 'Apply':

case 'Or':
return collectionDefinition.map((ifValue) => {
return ifValue;
});
return collectionDefinition.map((ifValue) => ifValue);
case 'String':
return collectionDefinition.map((stringValue) => {
if (typeof stringValue === 'string') {
if (typeof stringValue === 'string' || stringValue === undefined) {
return stringValue;
}
else if (stringValue === undefined) {
return stringValue;
}
else {

@@ -675,18 +589,26 @@ return stringValue.String;

}
function convertAnnotation(annotation, objectMap, context) {
if (annotation.record) {
return parseRecord(annotation.record, annotation.fullyQualifiedName, objectMap, context);
function isV4NavigationProperty(navProp) {
return !!navProp.targetTypeName;
}
/**
* Split the alias from the term value.
*
* @param references the current set of references
* @param termValue the value of the term
* @returns the term alias and the actual term value
*/
function splitTerm(references, termValue) {
return (0, utils_1.splitAtLast)((0, utils_1.alias)(references, termValue), '.');
}
function convertAnnotation(converter, target, rawAnnotation) {
var _a;
let annotation;
if (rawAnnotation.record) {
annotation = parseRecord(converter, rawAnnotation.term, target, '', rawAnnotation.__source, rawAnnotation.record, rawAnnotation.fullyQualifiedName);
}
else if (annotation.collection === undefined) {
if (annotation.value) {
return parseValue(annotation.value, annotation.fullyQualifiedName, objectMap, context);
}
else {
return true;
}
else if (rawAnnotation.collection === undefined) {
annotation = parseValue(converter, target, rawAnnotation.term, '', rawAnnotation.__source, (_a = rawAnnotation.value) !== null && _a !== void 0 ? _a : { type: 'Bool', Bool: true }, rawAnnotation.fullyQualifiedName);
}
else if (annotation.collection) {
const collection = parseCollection(annotation.collection, annotation.fullyQualifiedName, objectMap, context);
collection.fullyQualifiedName = annotation.fullyQualifiedName;
return collection;
else if (rawAnnotation.collection) {
annotation = parseCollection(converter, target, rawAnnotation.term, '', rawAnnotation.__source, rawAnnotation.collection, rawAnnotation.fullyQualifiedName);
}

@@ -696,443 +618,457 @@ else {

}
switch (typeof annotation) {
case 'string':
// eslint-disable-next-line no-new-wrappers
annotation = new String(annotation);
break;
case 'boolean':
// eslint-disable-next-line no-new-wrappers
annotation = new Boolean(annotation);
break;
case 'number':
annotation = new Number(annotation);
break;
default:
// do nothing
break;
}
annotation.fullyQualifiedName = rawAnnotation.fullyQualifiedName;
annotation[ANNOTATION_TARGET] = target;
const [vocAlias, vocTerm] = converter.splitTerm(rawAnnotation.term);
annotation.term = converter.unalias(`${vocAlias}.${vocTerm}`);
annotation.qualifier = rawAnnotation.qualifier;
annotation.__source = rawAnnotation.__source;
try {
(0, utils_1.lazy)(annotation, 'annotations', () => {
var _a;
const annotationFQN = annotation.fullyQualifiedName;
// be graceful when resolving annotations on annotations: Sometimes they are referenced directly, sometimes they
// are part of the global annotations list
let annotations;
if (rawAnnotation.annotations && rawAnnotation.annotations.length > 0) {
annotations = rawAnnotation.annotations;
}
else {
annotations = (_a = converter.rawAnnotationsPerTarget[annotationFQN]) === null || _a === void 0 ? void 0 : _a.annotations;
}
annotations === null || annotations === void 0 ? void 0 : annotations.forEach((rawSubAnnotation) => {
rawSubAnnotation.target = annotationFQN;
rawSubAnnotation.__source = annotation.__source;
rawSubAnnotation[ANNOTATION_TARGET] = target;
rawSubAnnotation.fullyQualifiedName = `${annotationFQN}@${rawSubAnnotation.term}`;
});
return createAnnotationsObject(converter, annotation, annotations !== null && annotations !== void 0 ? annotations : []);
});
}
catch (e) {
// not an error: parseRecord() already adds annotations, but the other parseXXX functions don't, so this can happen
}
return annotation;
}
function getAnnotationFQN(currentTargetName, references, annotation) {
const annotationFQN = `${currentTargetName}@${(0, utils_1.unalias)(references, annotation.term)}`;
if (annotation.qualifier) {
return `${annotationFQN}#${annotation.qualifier}`;
}
else {
return annotationFQN;
}
}
/**
* Creates a resolvePath function for a given entityType.
* Merge annotation from different source together by overwriting at the term level.
*
* @param entityType The entityType for which the function should be created
* @param objectMap The current objectMap
* @returns the resolvePath function that starts at the entityType
* @param rawMetadata
* @returns the resulting merged annotations
*/
function createResolvePathFn(entityType, objectMap) {
return function (relativePath, includeVisitedObjects) {
const annotationTerm = '';
return _resolveTarget(objectMap, entityType, relativePath, false, includeVisitedObjects, annotationTerm);
};
function mergeAnnotations(rawMetadata) {
const annotationListPerTarget = {};
Object.keys(rawMetadata.schema.annotations).forEach((annotationSource) => {
rawMetadata.schema.annotations[annotationSource].forEach((annotationList) => {
const currentTargetName = (0, utils_1.unalias)(rawMetadata.references, annotationList.target);
annotationList.__source = annotationSource;
if (!annotationListPerTarget[currentTargetName]) {
annotationListPerTarget[currentTargetName] = {
annotations: annotationList.annotations.map((annotation) => {
annotation.fullyQualifiedName = getAnnotationFQN(currentTargetName, rawMetadata.references, annotation);
annotation.__source = annotationSource;
return annotation;
}),
target: currentTargetName
};
annotationListPerTarget[currentTargetName].__source = annotationSource;
}
else {
annotationList.annotations.forEach((annotation) => {
const findIndex = annotationListPerTarget[currentTargetName].annotations.findIndex((referenceAnnotation) => {
return (referenceAnnotation.term === annotation.term &&
referenceAnnotation.qualifier === annotation.qualifier);
});
annotation.__source = annotationSource;
annotation.fullyQualifiedName = getAnnotationFQN(currentTargetName, rawMetadata.references, annotation);
if (findIndex !== -1) {
annotationListPerTarget[currentTargetName].annotations.splice(findIndex, 1, annotation);
}
else {
annotationListPerTarget[currentTargetName].annotations.push(annotation);
}
});
}
});
});
return annotationListPerTarget;
}
function resolveV2NavigationProperty(navProp, associations, objectMap, outNavProp) {
const targetAssociation = associations.find((association) => association.fullyQualifiedName === navProp.relationship);
if (targetAssociation) {
const associationEnd = targetAssociation.associationEnd.find((end) => end.role === navProp.toRole);
if (associationEnd) {
outNavProp.targetType = objectMap[associationEnd.type];
outNavProp.isCollection = associationEnd.multiplicity === '*';
class Converter {
get rawAnnotationsPerTarget() {
if (this._rawAnnotationsPerTarget === undefined) {
this._rawAnnotationsPerTarget = mergeAnnotations(this.rawMetadata);
}
return this._rawAnnotationsPerTarget;
}
outNavProp.referentialConstraint = navProp.referentialConstraint || [];
getConvertedEntityContainer() {
return this.getConvertedElement(this.rawMetadata.schema.entityContainer.fullyQualifiedName, this.rawMetadata.schema.entityContainer, convertEntityContainer);
}
getConvertedEntitySet(fullyQualifiedName) {
return this.convertedOutput.entitySets.by_fullyQualifiedName(fullyQualifiedName);
}
getConvertedSingleton(fullyQualifiedName) {
return this.convertedOutput.singletons.by_fullyQualifiedName(fullyQualifiedName);
}
getConvertedEntityType(fullyQualifiedName) {
return this.convertedOutput.entityTypes.by_fullyQualifiedName(fullyQualifiedName);
}
getConvertedComplexType(fullyQualifiedName) {
return this.convertedOutput.complexTypes.by_fullyQualifiedName(fullyQualifiedName);
}
getConvertedTypeDefinition(fullyQualifiedName) {
return this.convertedOutput.typeDefinitions.by_fullyQualifiedName(fullyQualifiedName);
}
getConvertedActionImport(fullyQualifiedName) {
return this.convertedOutput.actionImports.by_fullyQualifiedName(fullyQualifiedName);
}
getConvertedAction(fullyQualifiedName) {
return this.convertedOutput.actions.by_fullyQualifiedName(fullyQualifiedName);
}
convert(rawValue, map) {
if (Array.isArray(rawValue)) {
return () => {
const converted = rawValue.reduce((convertedElements, rawElement) => {
const convertedElement = this.getConvertedElement(rawElement.fullyQualifiedName, rawElement, map);
if (convertedElement) {
convertedElements.push(convertedElement);
}
return convertedElements;
}, []);
(0, utils_1.addGetByValue)(converted, 'name');
(0, utils_1.addGetByValue)(converted, 'fullyQualifiedName');
return converted;
};
}
else {
return () => this.getConvertedElement(rawValue.fullyQualifiedName, rawValue, map);
}
}
constructor(rawMetadata, convertedOutput) {
this.convertedElements = new Map();
this.rawMetadata = rawMetadata;
this.rawSchema = rawMetadata.schema;
this.convertedOutput = convertedOutput;
}
getConvertedElement(fullyQualifiedName, rawElement, map) {
let converted = this.convertedElements.get(fullyQualifiedName);
if (converted === undefined) {
const rawMetadata = typeof rawElement === 'function' ? rawElement.apply(undefined, [fullyQualifiedName]) : rawElement;
if (rawMetadata !== undefined) {
converted = map.apply(undefined, [this, rawMetadata]);
this.convertedElements.set(fullyQualifiedName, converted);
}
}
return converted;
}
logError(message) {
this.convertedOutput.diagnostics.push({ message });
}
splitTerm(term) {
return splitTerm(this.rawMetadata.references, term);
}
alias(value) {
return (0, utils_1.alias)(this.rawMetadata.references, value);
}
unalias(value) {
var _a;
return (_a = (0, utils_1.unalias)(this.rawMetadata.references, value)) !== null && _a !== void 0 ? _a : '';
}
}
function resolveV4NavigationProperty(navProp, objectMap, outNavProp) {
outNavProp.targetType = objectMap[navProp.targetTypeName];
outNavProp.partner = navProp.partner;
outNavProp.isCollection = navProp.isCollection;
outNavProp.containsTarget = navProp.containsTarget;
outNavProp.referentialConstraint = navProp.referentialConstraint;
function resolveEntityType(converter, fullyQualifiedName) {
return () => {
let entityType = converter.getConvertedEntityType(fullyQualifiedName);
if (!entityType) {
converter.logError(`EntityType '${fullyQualifiedName}' not found`);
entityType = {};
}
return entityType;
};
}
function isV4NavigationProperty(navProp) {
return !!navProp.targetTypeName;
function resolveNavigationPropertyBindings(converter, rawNavigationPropertyBindings, rawElement) {
return () => Object.keys(rawNavigationPropertyBindings).reduce((navigationPropertyBindings, bindingName) => {
const rawBindingTarget = rawNavigationPropertyBindings[bindingName];
(0, utils_1.lazy)(navigationPropertyBindings, bindingName, () => {
let resolvedBindingTarget;
if (rawBindingTarget._type === 'Singleton') {
resolvedBindingTarget = converter.getConvertedSingleton(rawBindingTarget.fullyQualifiedName);
}
else {
resolvedBindingTarget = converter.getConvertedEntitySet(rawBindingTarget.fullyQualifiedName);
}
if (!resolvedBindingTarget) {
converter.logError(`${rawElement._type} '${rawElement.fullyQualifiedName}': Failed to resolve NavigationPropertyBinding ${bindingName}`);
resolvedBindingTarget = {};
}
return resolvedBindingTarget;
});
return navigationPropertyBindings;
}, {});
}
function prepareNavigationProperties(navigationProperties, associations, objectMap) {
return navigationProperties.map((navProp) => {
const outNavProp = {
_type: 'NavigationProperty',
name: navProp.name,
fullyQualifiedName: navProp.fullyQualifiedName,
isCollection: false,
containsTarget: false,
referentialConstraint: [],
annotations: {},
partner: '',
targetType: undefined,
targetTypeName: ''
};
if (isV4NavigationProperty(navProp)) {
resolveV4NavigationProperty(navProp, objectMap, outNavProp);
function resolveAnnotations(converter, rawAnnotationTarget) {
const nestedAnnotations = rawAnnotationTarget.annotations;
return () => {
var _a, _b;
return createAnnotationsObject(converter, rawAnnotationTarget, (_b = nestedAnnotations !== null && nestedAnnotations !== void 0 ? nestedAnnotations : (_a = converter.rawAnnotationsPerTarget[rawAnnotationTarget.fullyQualifiedName]) === null || _a === void 0 ? void 0 : _a.annotations) !== null && _b !== void 0 ? _b : []);
};
}
function createAnnotationsObject(converter, target, rawAnnotations) {
return rawAnnotations.reduce((vocabularyAliases, annotation) => {
const [vocAlias, vocTerm] = converter.splitTerm(annotation.term);
const vocTermWithQualifier = `${vocTerm}${annotation.qualifier ? '#' + annotation.qualifier : ''}`;
if (vocabularyAliases[vocAlias] === undefined) {
vocabularyAliases[vocAlias] = {};
}
else {
resolveV2NavigationProperty(navProp, associations, objectMap, outNavProp);
if (!vocabularyAliases[vocAlias].hasOwnProperty(vocTermWithQualifier)) {
(0, utils_1.lazy)(vocabularyAliases[vocAlias], vocTermWithQualifier, () => converter.getConvertedElement(annotation.fullyQualifiedName, annotation, (converter, rawAnnotation) => convertAnnotation(converter, target, rawAnnotation)));
}
if (outNavProp.targetType) {
outNavProp.targetTypeName = outNavProp.targetType.fullyQualifiedName;
}
objectMap[outNavProp.fullyQualifiedName] = outNavProp;
return outNavProp;
});
return vocabularyAliases;
}, {});
}
/**
* @param entityTypes
* @param associations
* @param objectMap
* Converts an EntityContainer.
*
* @param converter Converter
* @param rawEntityContainer Unconverted EntityContainer
* @returns The converted EntityContainer
*/
function resolveNavigationProperties(entityTypes, associations, objectMap) {
entityTypes.forEach((entityType) => {
entityType.navigationProperties = prepareNavigationProperties(entityType.navigationProperties, associations, objectMap);
entityType.resolvePath = createResolvePathFn(entityType, objectMap);
});
function convertEntityContainer(converter, rawEntityContainer) {
const convertedEntityContainer = rawEntityContainer;
(0, utils_1.lazy)(convertedEntityContainer, 'annotations', resolveAnnotations(converter, rawEntityContainer));
(0, utils_1.lazy)(convertedEntityContainer, 'entitySets', converter.convert(converter.rawSchema.entitySets, convertEntitySet));
(0, utils_1.lazy)(convertedEntityContainer, 'singletons', converter.convert(converter.rawSchema.singletons, convertSingleton));
(0, utils_1.lazy)(convertedEntityContainer, 'actionImports', converter.convert(converter.rawSchema.actionImports, convertActionImport));
return convertedEntityContainer;
}
/**
* @param namespace
* @param actions
* @param objectMap
* Converts a Singleton.
*
* @param converter Converter
* @param rawSingleton Unconverted Singleton
* @returns The converted Singleton
*/
function linkActionsToEntityType(namespace, actions, objectMap) {
actions.forEach((action) => {
if (!action.annotations) {
action.annotations = {};
}
if (action.isBound) {
const sourceEntityType = objectMap[action.sourceType];
action.sourceEntityType = sourceEntityType;
if (sourceEntityType) {
if (!sourceEntityType.actions) {
sourceEntityType.actions = {};
}
sourceEntityType.actions[`${namespace}.${action.name}`] = action;
}
action.returnEntityType = objectMap[action.returnType];
}
});
function convertSingleton(converter, rawSingleton) {
const convertedSingleton = rawSingleton;
convertedSingleton.entityTypeName = converter.unalias(rawSingleton.entityTypeName);
(0, utils_1.lazy)(convertedSingleton, 'entityType', resolveEntityType(converter, rawSingleton.entityTypeName));
(0, utils_1.lazy)(convertedSingleton, 'annotations', resolveAnnotations(converter, rawSingleton));
const _rawNavigationPropertyBindings = rawSingleton.navigationPropertyBinding;
(0, utils_1.lazy)(convertedSingleton, 'navigationPropertyBinding', resolveNavigationPropertyBindings(converter, _rawNavigationPropertyBindings, rawSingleton));
return convertedSingleton;
}
function linkActionImportsToActions(actionImports, objectMap) {
actionImports.forEach((actionImport) => {
actionImport.action = objectMap[actionImport.actionName];
});
}
/**
* @param entitySets
* @param objectMap
* @param references
* Converts an EntitySet.
*
* @param converter Converter
* @param rawEntitySet Unconverted EntitySet
* @returns The converted EntitySet
*/
function linkEntityTypeToEntitySet(entitySets, objectMap, references) {
entitySets.forEach((entitySet) => {
entitySet.entityType = objectMap[entitySet.entityTypeName];
if (!entitySet.entityType) {
entitySet.entityType = objectMap[(0, utils_1.unalias)(references, entitySet.entityTypeName)];
}
if (!entitySet.annotations) {
entitySet.annotations = {};
}
if (!entitySet.entityType.annotations) {
entitySet.entityType.annotations = {};
}
entitySet.entityType.keys.forEach((keyProp) => {
keyProp.isKey = true;
});
});
function convertEntitySet(converter, rawEntitySet) {
const convertedEntitySet = rawEntitySet;
convertedEntitySet.entityTypeName = converter.unalias(rawEntitySet.entityTypeName);
(0, utils_1.lazy)(convertedEntitySet, 'entityType', resolveEntityType(converter, rawEntitySet.entityTypeName));
(0, utils_1.lazy)(convertedEntitySet, 'annotations', resolveAnnotations(converter, rawEntitySet));
const _rawNavigationPropertyBindings = rawEntitySet.navigationPropertyBinding;
(0, utils_1.lazy)(convertedEntitySet, 'navigationPropertyBinding', resolveNavigationPropertyBindings(converter, _rawNavigationPropertyBindings, rawEntitySet));
return convertedEntitySet;
}
/**
* @param singletons
* @param objectMap
* @param references
* Converts an EntityType.
*
* @param converter Converter
* @param rawEntityType Unconverted EntityType
* @returns The converted EntityType
*/
function linkEntityTypeToSingleton(singletons, objectMap, references) {
singletons.forEach((singleton) => {
singleton.entityType = objectMap[singleton.entityTypeName];
if (!singleton.entityType) {
singleton.entityType = objectMap[(0, utils_1.unalias)(references, singleton.entityTypeName)];
function convertEntityType(converter, rawEntityType) {
const convertedEntityType = rawEntityType;
rawEntityType.keys.forEach((keyProp) => {
keyProp.isKey = true;
});
(0, utils_1.lazy)(convertedEntityType, 'annotations', resolveAnnotations(converter, rawEntityType));
(0, utils_1.lazy)(convertedEntityType, 'keys', converter.convert(rawEntityType.keys, convertProperty));
(0, utils_1.lazy)(convertedEntityType, 'entityProperties', converter.convert(rawEntityType.entityProperties, convertProperty));
(0, utils_1.lazy)(convertedEntityType, 'navigationProperties', converter.convert(rawEntityType.navigationProperties, convertNavigationProperty));
(0, utils_1.lazy)(convertedEntityType, 'actions', () => converter.rawSchema.actions
.filter((rawAction) => rawAction.isBound &&
(rawAction.sourceType === rawEntityType.fullyQualifiedName ||
rawAction.sourceType === `Collection(${rawEntityType.fullyQualifiedName})`))
.reduce((actions, rawAction) => {
const name = `${converter.rawSchema.namespace}.${rawAction.name}`;
actions[name] = converter.getConvertedAction(rawAction.fullyQualifiedName);
return actions;
}, {}));
convertedEntityType.resolvePath = (relativePath, includeVisitedObjects) => {
const resolved = resolveTarget(converter, rawEntityType, relativePath);
if (includeVisitedObjects) {
return { target: resolved.target, visitedObjects: resolved.objectPath, messages: resolved.messages };
}
if (!singleton.annotations) {
singleton.annotations = {};
else {
return resolved.target;
}
if (!singleton.entityType.annotations) {
singleton.entityType.annotations = {};
}
singleton.entityType.keys.forEach((keyProp) => {
keyProp.isKey = true;
});
});
};
return convertedEntityType;
}
/**
* @param entityTypes
* @param objectMap
* Converts a Property.
*
* @param converter Converter
* @param rawProperty Unconverted Property
* @returns The converted Property
*/
function linkPropertiesToComplexTypes(entityTypes, objectMap) {
/**
* @param property
*/
function link(property) {
if (!property.annotations) {
property.annotations = {};
}
try {
if (!property.type.startsWith('Edm')) {
let complexType;
if (property.type.startsWith('Collection')) {
const complexTypeName = property.type.substring(11, property.type.length - 1);
complexType = objectMap[complexTypeName];
}
else {
complexType = objectMap[property.type];
}
if (complexType) {
property.targetType = complexType;
if (complexType.properties) {
complexType.properties.forEach(link);
}
}
}
}
catch (sError) {
throw new Error('Property Type is not defined');
}
}
entityTypes.forEach((entityType) => {
entityType.entityProperties.forEach(link);
function convertProperty(converter, rawProperty) {
const convertedProperty = rawProperty;
convertedProperty.type = converter.unalias(rawProperty.type);
(0, utils_1.lazy)(convertedProperty, 'annotations', resolveAnnotations(converter, rawProperty));
(0, utils_1.lazy)(convertedProperty, 'targetType', () => {
var _a;
const type = rawProperty.type;
const typeName = type.startsWith('Collection') ? type.substring(11, type.length - 1) : type;
return (_a = converter.getConvertedComplexType(typeName)) !== null && _a !== void 0 ? _a : converter.getConvertedTypeDefinition(typeName);
});
return convertedProperty;
}
/**
* @param complexTypes
* @param associations
* @param objectMap
* Converts a NavigationProperty.
*
* @param converter Converter
* @param rawNavigationProperty Unconverted NavigationProperty
* @returns The converted NavigationProperty
*/
function prepareComplexTypes(complexTypes, associations, objectMap) {
complexTypes.forEach((complexType) => {
complexType.annotations = {};
complexType.properties.forEach((property) => {
if (!property.annotations) {
property.annotations = {};
}
});
complexType.navigationProperties = prepareNavigationProperties(complexType.navigationProperties, associations, objectMap);
});
function convertNavigationProperty(converter, rawNavigationProperty) {
var _a, _b, _c;
const convertedNavigationProperty = rawNavigationProperty;
convertedNavigationProperty.referentialConstraint = (_a = convertedNavigationProperty.referentialConstraint) !== null && _a !== void 0 ? _a : [];
if (isV4NavigationProperty(rawNavigationProperty)) {
convertedNavigationProperty.targetTypeName = converter.unalias(rawNavigationProperty.targetTypeName);
}
else {
const associationEnd = (_b = converter.rawSchema.associations
.find((association) => association.fullyQualifiedName === rawNavigationProperty.relationship)) === null || _b === void 0 ? void 0 : _b.associationEnd.find((end) => end.role === rawNavigationProperty.toRole);
convertedNavigationProperty.isCollection = (associationEnd === null || associationEnd === void 0 ? void 0 : associationEnd.multiplicity) === '*';
convertedNavigationProperty.targetTypeName = (_c = associationEnd === null || associationEnd === void 0 ? void 0 : associationEnd.type) !== null && _c !== void 0 ? _c : '';
}
(0, utils_1.lazy)(convertedNavigationProperty, 'targetType', resolveEntityType(converter, rawNavigationProperty.targetTypeName));
(0, utils_1.lazy)(convertedNavigationProperty, 'annotations', resolveAnnotations(converter, rawNavigationProperty));
return convertedNavigationProperty;
}
/**
* Split the alias from the term value.
* Converts an ActionImport.
*
* @param references the current set of references
* @param termValue the value of the term
* @returns the term alias and the actual term value
* @param converter Converter
* @param rawActionImport Unconverted ActionImport
* @returns The converted ActionImport
*/
function splitTerm(references, termValue) {
return (0, utils_1.splitAtLast)((0, utils_1.alias)(references, termValue), '.');
function convertActionImport(converter, rawActionImport) {
const convertedActionImport = rawActionImport;
convertedActionImport.actionName = converter.unalias(rawActionImport.actionName);
(0, utils_1.lazy)(convertedActionImport, 'annotations', resolveAnnotations(converter, rawActionImport));
(0, utils_1.lazy)(convertedActionImport, 'action', () => converter.getConvertedAction(rawActionImport.actionName));
return convertedActionImport;
}
/**
* Creates the function that will resolve a specific path.
* Converts an Action.
*
* @param convertedOutput
* @param objectMap
* @returns the function that will allow to resolve element globally.
* @param converter Converter
* @param rawAction Unconverted Action
* @returns The converted Action
*/
function createGlobalResolve(convertedOutput, objectMap) {
return function resolvePath(sPath, resolveDirectly = false) {
if (resolveDirectly) {
let targetPath = sPath;
if (!sPath.startsWith('/')) {
targetPath = `/${sPath}`;
function convertAction(converter, rawAction) {
const convertedAction = rawAction;
convertedAction.sourceType = converter.unalias(rawAction.sourceType);
if (convertedAction.sourceType) {
(0, utils_1.lazy)(convertedAction, 'sourceEntityType', resolveEntityType(converter, rawAction.sourceType));
}
convertedAction.returnType = converter.unalias(rawAction.returnType);
if (convertedAction.returnType) {
(0, utils_1.lazy)(convertedAction, 'returnEntityType', resolveEntityType(converter, rawAction.returnType));
}
(0, utils_1.lazy)(convertedAction, 'parameters', converter.convert(rawAction.parameters, convertActionParameter));
(0, utils_1.lazy)(convertedAction, 'annotations', () => {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
// this.is.the.action(on.this.type) --> action: 'this.is.the.action', overload: 'on.this.type'
// this.is.the.action() --> action: 'this.is.the.action', overload: undefined
// this.is.the.action --> action: 'this.is.the.action', overload: undefined
const actionAndOverload = rawAction.fullyQualifiedName.match(/(?<action>[^()]+)(?:\((?<overload>.*)\))?/);
let rawAnnotations = [];
if (actionAndOverload) {
if ((_a = actionAndOverload.groups) === null || _a === void 0 ? void 0 : _a.overload) {
rawAnnotations = (_c = (_b = converter.rawAnnotationsPerTarget[rawAction.fullyQualifiedName]) === null || _b === void 0 ? void 0 : _b.annotations) !== null && _c !== void 0 ? _c : [];
}
const targetResolution = _resolveTarget(objectMap, convertedOutput, targetPath, false, true);
if (targetResolution.target) {
targetResolution.visitedObjects.push(targetResolution.target);
else {
rawAnnotations =
(_f = (_e = converter.rawAnnotationsPerTarget[`${(_d = actionAndOverload.groups) === null || _d === void 0 ? void 0 : _d.action}()`]) === null || _e === void 0 ? void 0 : _e.annotations) !== null && _f !== void 0 ? _f : [];
}
return {
target: targetResolution.target,
objectPath: targetResolution.visitedObjects
};
}
const aPathSplit = sPath.split('/');
if (aPathSplit.shift() !== '') {
throw new Error('Cannot deal with relative path');
}
const entitySetName = aPathSplit.shift();
const entitySet = convertedOutput.entitySets.find((et) => et.name === entitySetName);
const singleton = convertedOutput.singletons.find((et) => et.name === entitySetName);
if (!entitySet && !singleton) {
return {
target: convertedOutput.entityContainer,
objectPath: [convertedOutput.entityContainer]
};
}
if (aPathSplit.length === 0) {
return {
target: entitySet || singleton,
objectPath: [convertedOutput.entityContainer, entitySet || singleton]
};
}
else {
const targetResolution = _resolveTarget(objectMap, entitySet || singleton, '/' + aPathSplit.join('/'), false, true);
if (targetResolution.target) {
targetResolution.visitedObjects.push(targetResolution.target);
if (((_g = actionAndOverload.groups) === null || _g === void 0 ? void 0 : _g.action) && ((_h = actionAndOverload.groups) === null || _h === void 0 ? void 0 : _h.action) !== rawAction.fullyQualifiedName) {
const baseAnnotations = (_l = (_k = converter.rawAnnotationsPerTarget[(_j = actionAndOverload.groups) === null || _j === void 0 ? void 0 : _j.action]) === null || _k === void 0 ? void 0 : _k.annotations) !== null && _l !== void 0 ? _l : [];
rawAnnotations = rawAnnotations.concat(baseAnnotations);
}
return {
target: targetResolution.target,
objectPath: targetResolution.visitedObjects
};
}
};
return createAnnotationsObject(converter, rawAction, rawAnnotations);
});
return convertedAction;
}
function ensureAnnotations(currentTarget, vocAlias) {
if (!currentTarget.annotations) {
currentTarget.annotations = {};
}
if (!currentTarget.annotations[vocAlias]) {
currentTarget.annotations[vocAlias] = {};
}
if (!currentTarget.annotations._annotations) {
currentTarget.annotations._annotations = {};
}
}
function processAnnotations(currentContext, annotationList, objectMap, bOverrideExisting) {
const currentTarget = currentContext.currentTarget;
const currentTargetName = currentTarget.fullyQualifiedName;
annotationList.annotations.forEach((annotation) => {
/**
* Converts an ActionParameter.
*
* @param converter Converter
* @param rawActionParameter Unconverted ActionParameter
* @returns The converted ActionParameter
*/
function convertActionParameter(converter, rawActionParameter) {
const convertedActionParameter = rawActionParameter;
(0, utils_1.lazy)(convertedActionParameter, 'typeReference', () => {
var _a, _b;
currentContext.currentSource = annotation.__source || annotationList.__source;
const [vocAlias, vocTerm] = splitTerm(utils_1.defaultReferences, annotation.term);
ensureAnnotations(currentTarget, vocAlias);
const vocTermWithQualifier = `${vocTerm}${annotation.qualifier ? '#' + annotation.qualifier : ''}`;
if (!bOverrideExisting && ((_b = (_a = currentTarget.annotations) === null || _a === void 0 ? void 0 : _a[vocAlias]) === null || _b === void 0 ? void 0 : _b[vocTermWithQualifier]) !== undefined) {
return;
}
currentContext.currentTerm = annotation.term;
currentTarget.annotations[vocAlias][vocTermWithQualifier] = convertAnnotation(annotation, objectMap, currentContext);
switch (typeof currentTarget.annotations[vocAlias][vocTermWithQualifier]) {
case 'string':
// eslint-disable-next-line no-new-wrappers
currentTarget.annotations[vocAlias][vocTermWithQualifier] = new String(currentTarget.annotations[vocAlias][vocTermWithQualifier]);
break;
case 'boolean':
// eslint-disable-next-line no-new-wrappers
currentTarget.annotations[vocAlias][vocTermWithQualifier] = new Boolean(currentTarget.annotations[vocAlias][vocTermWithQualifier]);
break;
default:
// do nothing
break;
}
if (currentTarget.annotations[vocAlias][vocTermWithQualifier] !== null &&
typeof currentTarget.annotations[vocAlias][vocTermWithQualifier] === 'object' &&
!currentTarget.annotations[vocAlias][vocTermWithQualifier].annotations) {
currentTarget.annotations[vocAlias][vocTermWithQualifier].annotations = {};
}
if (currentTarget.annotations[vocAlias][vocTermWithQualifier] !== null &&
typeof currentTarget.annotations[vocAlias][vocTermWithQualifier] === 'object') {
currentTarget.annotations[vocAlias][vocTermWithQualifier].term = (0, utils_1.unalias)(utils_1.defaultReferences, `${vocAlias}.${vocTerm}`);
currentTarget.annotations[vocAlias][vocTermWithQualifier].qualifier = annotation.qualifier;
currentTarget.annotations[vocAlias][vocTermWithQualifier].__source = currentContext.currentSource;
}
const annotationTarget = `${currentTargetName}@${(0, utils_1.unalias)(utils_1.defaultReferences, vocAlias + '.' + vocTermWithQualifier)}`;
if (Array.isArray(annotation.annotations)) {
const subAnnotationList = {
target: annotationTarget,
annotations: annotation.annotations,
__source: currentContext.currentSource
};
currentContext.additionalAnnotations.push(subAnnotationList);
}
else if (annotation.annotations && !currentTarget.annotations[vocAlias][vocTermWithQualifier].annotations) {
currentTarget.annotations[vocAlias][vocTermWithQualifier].annotations = annotation.annotations;
}
currentTarget.annotations._annotations[`${vocAlias}.${vocTermWithQualifier}`] =
currentTarget.annotations._annotations[(0, utils_1.unalias)(utils_1.defaultReferences, `${vocAlias}.${vocTermWithQualifier}`)] =
currentTarget.annotations[vocAlias][vocTermWithQualifier];
objectMap[annotationTarget] = currentTarget.annotations[vocAlias][vocTermWithQualifier];
return (_b = (_a = converter.getConvertedEntityType(rawActionParameter.type)) !== null && _a !== void 0 ? _a : converter.getConvertedComplexType(rawActionParameter.type)) !== null && _b !== void 0 ? _b : converter.getConvertedTypeDefinition(rawActionParameter.type);
});
(0, utils_1.lazy)(convertedActionParameter, 'annotations', resolveAnnotations(converter, rawActionParameter));
return convertedActionParameter;
}
/**
* Process all the unresolved targets so far to try and see if they are resolveable in the end.
* Converts a ComplexType.
*
* @param unresolvedTargets
* @param objectMap
* @param converter Converter
* @param rawComplexType Unconverted ComplexType
* @returns The converted ComplexType
*/
function processUnresolvedTargets(unresolvedTargets, objectMap) {
unresolvedTargets.forEach((resolvable) => {
const targetToResolve = resolvable.toResolve;
const targetStr = targetToResolve.$target;
const resolvedTarget = objectMap[targetStr];
const { annotationsTerm, annotationType } = targetToResolve;
delete targetToResolve.annotationType;
delete targetToResolve.annotationsTerm;
if (resolvable.inline && !(resolvedTarget instanceof String)) {
// inline the resolved target
let keys;
for (keys in targetToResolve) {
delete targetToResolve[keys];
}
Object.assign(targetToResolve, resolvedTarget);
}
else {
// assign the resolved target
targetToResolve.$target = resolvedTarget;
}
if (!resolvedTarget) {
targetToResolve.targetString = targetStr;
if (annotationsTerm && annotationType) {
const oErrorMsg = {
message: 'Unable to resolve the path expression: ' +
targetStr +
'\n' +
'\n' +
'Hint: Check and correct the path values under the following structure in the metadata (annotation.xml file or CDS annotations for the application): \n\n' +
'<Annotation Term = ' +
annotationsTerm +
'>' +
'\n' +
'<Record Type = ' +
annotationType +
'>' +
'\n' +
'<AnnotationPath = ' +
targetStr +
'>'
};
addAnnotationErrorMessage(targetStr, oErrorMsg);
}
else {
const property = targetToResolve.term;
const path = targetToResolve.path;
const termInfo = (0, utils_1.substringBeforeFirst)(targetStr, '/');
const oErrorMsg = {
message: 'Unable to resolve the path expression: ' +
targetStr +
'\n' +
'\n' +
'Hint: Check and correct the path values under the following structure in the metadata (annotation.xml file or CDS annotations for the application): \n\n' +
'<Annotation Term = ' +
termInfo +
'>' +
'\n' +
'<PropertyValue Property = ' +
property +
' Path= ' +
path +
'>'
};
addAnnotationErrorMessage(targetStr, oErrorMsg);
}
}
});
function convertComplexType(converter, rawComplexType) {
const convertedComplexType = rawComplexType;
(0, utils_1.lazy)(convertedComplexType, 'properties', converter.convert(rawComplexType.properties, convertProperty));
(0, utils_1.lazy)(convertedComplexType, 'navigationProperties', converter.convert(rawComplexType.navigationProperties, convertNavigationProperty));
(0, utils_1.lazy)(convertedComplexType, 'annotations', resolveAnnotations(converter, rawComplexType));
return convertedComplexType;
}
/**
* Merge annotation from different source together by overwriting at the term level.
* Converts a TypeDefinition.
*
* @param rawMetadata
* @returns the resulting merged annotations
* @param converter Converter
* @param rawTypeDefinition Unconverted TypeDefinition
* @returns The converted TypeDefinition
*/
function mergeAnnotations(rawMetadata) {
const annotationListPerTarget = {};
Object.keys(rawMetadata.schema.annotations).forEach((annotationSource) => {
rawMetadata.schema.annotations[annotationSource].forEach((annotationList) => {
const currentTargetName = (0, utils_1.unalias)(rawMetadata.references, annotationList.target);
annotationList.__source = annotationSource;
if (!annotationListPerTarget[currentTargetName]) {
annotationListPerTarget[currentTargetName] = {
annotations: annotationList.annotations.concat(),
target: currentTargetName
};
annotationListPerTarget[currentTargetName].__source = annotationSource;
}
else {
annotationList.annotations.forEach((annotation) => {
const findIndex = annotationListPerTarget[currentTargetName].annotations.findIndex((referenceAnnotation) => {
return (referenceAnnotation.term === annotation.term &&
referenceAnnotation.qualifier === annotation.qualifier);
});
annotation.__source = annotationSource;
if (findIndex !== -1) {
annotationListPerTarget[currentTargetName].annotations.splice(findIndex, 1, annotation);
}
else {
annotationListPerTarget[currentTargetName].annotations.push(annotation);
}
});
}
});
});
return annotationListPerTarget;
function convertTypeDefinition(converter, rawTypeDefinition) {
const convertedTypeDefinition = rawTypeDefinition;
(0, utils_1.lazy)(convertedTypeDefinition, 'annotations', resolveAnnotations(converter, rawTypeDefinition));
return convertedTypeDefinition;
}

@@ -1146,91 +1082,31 @@ /**

function convert(rawMetadata) {
ANNOTATION_ERRORS = [];
const objectMap = buildObjectMap(rawMetadata);
resolveNavigationProperties(rawMetadata.schema.entityTypes, rawMetadata.schema.associations, objectMap);
rawMetadata.schema.entityContainer.annotations = {};
linkActionsToEntityType(rawMetadata.schema.namespace, rawMetadata.schema.actions, objectMap);
linkActionImportsToActions(rawMetadata.schema.actionImports, objectMap);
linkEntityTypeToEntitySet(rawMetadata.schema.entitySets, objectMap, rawMetadata.references);
linkEntityTypeToSingleton(rawMetadata.schema.singletons, objectMap, rawMetadata.references);
linkPropertiesToComplexTypes(rawMetadata.schema.entityTypes, objectMap);
prepareComplexTypes(rawMetadata.schema.complexTypes, rawMetadata.schema.associations, objectMap);
const unresolvedTargets = [];
const unresolvedAnnotations = [];
const annotationListPerTarget = mergeAnnotations(rawMetadata);
Object.keys(annotationListPerTarget).forEach((currentTargetName) => {
const annotationList = annotationListPerTarget[currentTargetName];
const objectMapElement = objectMap[currentTargetName];
if (!objectMapElement && (currentTargetName === null || currentTargetName === void 0 ? void 0 : currentTargetName.includes('@'))) {
unresolvedAnnotations.push(annotationList);
}
else if (objectMapElement) {
let allTargets = [objectMapElement];
let bOverrideExisting = true;
if (objectMapElement._type === 'UnboundGenericAction') {
allTargets = objectMapElement.actions;
bOverrideExisting = false;
}
allTargets.forEach((currentTarget) => {
const currentContext = {
additionalAnnotations: unresolvedAnnotations,
currentSource: annotationList.__source,
currentTarget: currentTarget,
currentTerm: '',
rawMetadata: rawMetadata,
unresolvedAnnotations: unresolvedTargets
};
processAnnotations(currentContext, annotationList, objectMap, bOverrideExisting);
});
}
});
const extraUnresolvedAnnotations = [];
unresolvedAnnotations.forEach((annotationList) => {
const currentTargetName = (0, utils_1.unalias)(rawMetadata.references, annotationList.target);
let [baseObj, annotationPart] = (0, utils_1.splitAtFirst)(currentTargetName, '@');
const targetSplit = annotationPart.split('/');
baseObj = baseObj + '@' + targetSplit.shift();
const currentTarget = targetSplit.reduce((currentObj, path) => {
return currentObj === null || currentObj === void 0 ? void 0 : currentObj[path];
}, objectMap[baseObj]);
if (!currentTarget || typeof currentTarget !== 'object') {
ANNOTATION_ERRORS.push({
message: 'The following annotation target was not found on the service ' + currentTargetName
});
}
else {
const currentContext = {
additionalAnnotations: extraUnresolvedAnnotations,
currentSource: annotationList.__source,
currentTarget: currentTarget,
currentTerm: '',
rawMetadata: rawMetadata,
unresolvedAnnotations: unresolvedTargets
};
processAnnotations(currentContext, annotationList, objectMap, false);
}
});
processUnresolvedTargets(unresolvedTargets, objectMap);
for (const property in ALL_ANNOTATION_ERRORS) {
ANNOTATION_ERRORS.push(ALL_ANNOTATION_ERRORS[property][0]);
// fall back on the default references if the caller does not specify any
if (rawMetadata.references.length === 0) {
rawMetadata.references = utils_1.defaultReferences;
}
rawMetadata.entitySets = rawMetadata.schema.entitySets;
const extraReferences = rawMetadata.references.filter((reference) => {
return utils_1.defaultReferences.find((defaultRef) => defaultRef.namespace === reference.namespace) === undefined;
});
// Converter Output
const convertedOutput = {
version: rawMetadata.version,
namespace: rawMetadata.schema.namespace,
annotations: rawMetadata.schema.annotations,
namespace: rawMetadata.schema.namespace,
entityContainer: rawMetadata.schema.entityContainer,
actions: rawMetadata.schema.actions,
actionImports: rawMetadata.schema.actionImports,
entitySets: rawMetadata.schema.entitySets,
singletons: rawMetadata.schema.singletons,
entityTypes: rawMetadata.schema.entityTypes,
complexTypes: rawMetadata.schema.complexTypes,
typeDefinitions: rawMetadata.schema.typeDefinitions,
references: utils_1.defaultReferences.concat(extraReferences),
diagnostics: ANNOTATION_ERRORS.concat()
references: utils_1.defaultReferences.concat(rawMetadata.references),
diagnostics: []
};
convertedOutput.resolvePath = createGlobalResolve(convertedOutput, objectMap);
// Converter
const converter = new Converter(rawMetadata, convertedOutput);
(0, utils_1.lazy)(convertedOutput, 'entityContainer', converter.convert(converter.rawSchema.entityContainer, convertEntityContainer));
(0, utils_1.lazy)(convertedOutput, 'entitySets', converter.convert(converter.rawSchema.entitySets, convertEntitySet));
(0, utils_1.lazy)(convertedOutput, 'singletons', converter.convert(converter.rawSchema.singletons, convertSingleton));
(0, utils_1.lazy)(convertedOutput, 'entityTypes', converter.convert(converter.rawSchema.entityTypes, convertEntityType));
(0, utils_1.lazy)(convertedOutput, 'actions', converter.convert(converter.rawSchema.actions, convertAction));
(0, utils_1.lazy)(convertedOutput, 'complexTypes', converter.convert(converter.rawSchema.complexTypes, convertComplexType));
(0, utils_1.lazy)(convertedOutput, 'actionImports', converter.convert(converter.rawSchema.actionImports, convertActionImport));
(0, utils_1.lazy)(convertedOutput, 'typeDefinitions', converter.convert(converter.rawSchema.typeDefinitions, convertTypeDefinition));
convertedOutput.resolvePath = function resolvePath(path) {
const targetResolution = resolveTarget(converter, undefined, path);
if (targetResolution.target) {
appendObjectPath(targetResolution.objectPath, targetResolution.target);
}
return targetResolution;
};
return convertedOutput;

@@ -1237,0 +1113,0 @@ }

@@ -1,2 +0,2 @@

import type { ComplexType, Reference, TypeDefinition } from '@sap-ux/vocabularies-types';
import type { ComplexType, Reference, TypeDefinition, ArrayWithIndex } from '@sap-ux/vocabularies-types';
export declare const defaultReferences: ReferencesWithMap;

@@ -309,2 +309,3 @@ export type ReferencesWithMap = Reference[] & {

* Differentiate between a ComplexType and a TypeDefinition.
*
* @param complexTypeDefinition

@@ -319,2 +320,31 @@ * @returns true if the value is a complex type

};
/**
* Defines a lazy property.
*
* The property is initialized by calling the init function on the first read access, or by directly assigning a value.
*
* @param object The host object
* @param property The lazy property to add
* @param init The function that initializes the property's value
*/
export declare function lazy<Type, Key extends keyof Type>(object: Type, property: Key, init: () => Type[Key]): void;
/**
* Creates a function that allows to find an array element by property value.
*
* @param array The array
* @param property Elements in the array are searched by this property
* @returns A function that can be used to find an element of the array by property value.
*/
export declare function createIndexedFind<T>(array: Array<T>, property: keyof T): (value: T[keyof T]) => T | undefined;
/**
* Adds a 'get by value' function to an array.
*
* If this function is called with addIndex(myArray, 'name'), a new function 'by_name(value)' will be added that allows to
* find a member of the array by the value of its 'name' property.
*
* @param array The array
* @param property The property that will be used by the 'by_{property}()' function
* @returns The array with the added function
*/
export declare function addGetByValue<T, P extends Extract<keyof T, string>>(array: Array<T>, property: P): ArrayWithIndex<T, P>;
//# sourceMappingURL=utils.d.ts.map
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Decimal = exports.isComplexTypeDefinition = exports.TermToTypes = exports.EnumIsFlag = exports.unalias = exports.alias = exports.substringBeforeLast = exports.substringBeforeFirst = exports.splitAtLast = exports.splitAtFirst = exports.defaultReferences = void 0;
exports.addGetByValue = exports.createIndexedFind = exports.lazy = exports.Decimal = exports.isComplexTypeDefinition = exports.TermToTypes = exports.EnumIsFlag = exports.unalias = exports.alias = exports.substringBeforeLast = exports.substringBeforeFirst = exports.splitAtLast = exports.splitAtFirst = exports.defaultReferences = void 0;
exports.defaultReferences = [

@@ -422,2 +422,3 @@ { alias: 'Capabilities', namespace: 'Org.OData.Capabilities.V1', uri: '' },

* Differentiate between a ComplexType and a TypeDefinition.
*
* @param complexTypeDefinition

@@ -444,2 +445,74 @@ * @returns true if the value is a complex type

exports.Decimal = Decimal;
/**
* Defines a lazy property.
*
* The property is initialized by calling the init function on the first read access, or by directly assigning a value.
*
* @param object The host object
* @param property The lazy property to add
* @param init The function that initializes the property's value
*/
function lazy(object, property, init) {
const initial = Symbol('initial');
let _value = initial;
Object.defineProperty(object, property, {
enumerable: true,
get() {
if (_value === initial) {
_value = init();
}
return _value;
},
set(value) {
_value = value;
}
});
}
exports.lazy = lazy;
/**
* Creates a function that allows to find an array element by property value.
*
* @param array The array
* @param property Elements in the array are searched by this property
* @returns A function that can be used to find an element of the array by property value.
*/
function createIndexedFind(array, property) {
const index = new Map();
return function find(value) {
const element = index.get(value);
if ((element === null || element === void 0 ? void 0 : element[property]) === value) {
return element;
}
return array.find((element) => {
if (!(element === null || element === void 0 ? void 0 : element.hasOwnProperty(property))) {
return false;
}
const propertyValue = element[property];
index.set(propertyValue, element);
return propertyValue === value;
});
};
}
exports.createIndexedFind = createIndexedFind;
/**
* Adds a 'get by value' function to an array.
*
* If this function is called with addIndex(myArray, 'name'), a new function 'by_name(value)' will be added that allows to
* find a member of the array by the value of its 'name' property.
*
* @param array The array
* @param property The property that will be used by the 'by_{property}()' function
* @returns The array with the added function
*/
function addGetByValue(array, property) {
const indexName = `by_${property}`;
if (!array.hasOwnProperty(indexName)) {
Object.defineProperty(array, indexName, { writable: false, value: createIndexedFind(array, property) });
}
else {
throw new Error(`Property '${indexName}' already exists`);
}
return array;
}
exports.addGetByValue = addGetByValue;
//# sourceMappingURL=utils.js.map
{
"name": "@sap-ux/annotation-converter",
"version": "0.5.23",
"version": "0.6.0",
"description": "SAP Fiori OData - Annotation converter",

@@ -17,3 +17,3 @@ "repository": {

"devDependencies": {
"@sap-ux/vocabularies-types": "0.6.9",
"@sap-ux/vocabularies-types": "0.7.0",
"@sap-ux/edmx-parser": "0.5.13"

@@ -20,0 +20,0 @@ },

import type {
Action,
ActionImport,
ActionParameter,
Annotation,
AnnotationList,
AnnotationRecord,
ArrayWithIndex,
BaseNavigationProperty,

@@ -14,15 +16,22 @@ ComplexType,

Expression,
FullyQualifiedName,
NavigationProperty,
PathExpression,
Property,
PropertyValue,
PropertyPath,
RawAction,
RawActionImport,
RawAnnotation,
RawAssociation,
RawComplexType,
RawEntityContainer,
RawEntitySet,
RawEntityType,
RawMetadata,
RawProperty,
RawSchema,
RawSingleton,
RawTypeDefinition,
RawV2NavigationProperty,
RawV4NavigationProperty,
Reference,
RemoveAnnotationAndType,
ResolutionTarget,

@@ -34,2 +43,3 @@ Singleton,

import {
addGetByValue,
alias,

@@ -39,3 +49,3 @@ Decimal,

EnumIsFlag,
isComplexTypeDefinition,
lazy,
splitAtFirst,

@@ -50,322 +60,293 @@ splitAtLast,

/**
* Symbol to extend an annotation with the reference to its target.
*/
const ANNOTATION_TARGET = Symbol('Annotation Target');
/**
* Append an object to the list of visited objects if it is different from the last object in the list.
*
* @param objectPath The list of visited objects
* @param visitedObject The object
* @returns The list of visited objects
*/
class Path {
path: string;
$target: string;
type: string;
annotationsTerm: string;
annotationType: string;
term: string;
/**
* @param pathExpression
* @param targetName
* @param annotationsTerm
* @param term
*/
constructor(pathExpression: PathExpression, targetName: string, annotationsTerm: string, term: string) {
this.path = pathExpression.Path;
this.type = 'Path';
this.$target = targetName;
this.term = term;
this.annotationsTerm = annotationsTerm;
function appendObjectPath(objectPath: any[], visitedObject: any): any[] {
if (objectPath[objectPath.length - 1] !== visitedObject) {
objectPath.push(visitedObject);
}
return objectPath;
}
/**
* Creates a Map based on the fullyQualifiedName of each object part of the metadata.
* Resolves a (possibly relative) path.
*
* @param rawMetadata the rawMetadata we're working against
* @returns the objectmap for easy access to the different object of the metadata
* @param converter Converter
* @param startElement The starting point in case of relative path resolution
* @param path The path to resolve
* @param annotationsTerm Only for error reporting: The annotation term
* @returns An object containing the resolved target and the elements that were visited while getting to the target.
*/
function buildObjectMap(rawMetadata: RawMetadata): Record<string, any> {
const objectMap: any = {};
if (rawMetadata.schema.entityContainer?.fullyQualifiedName) {
objectMap[rawMetadata.schema.entityContainer.fullyQualifiedName] = rawMetadata.schema.entityContainer;
function resolveTarget<T>(
converter: Converter,
startElement: any,
path: string,
annotationsTerm?: string
): ResolutionTarget<T> {
// absolute paths always start at the entity container
if (path.startsWith('/')) {
path = path.substring(1);
startElement = undefined; // will resolve to the entity container (see below)
}
for (const entitySet of rawMetadata.schema.entitySets) {
objectMap[entitySet.fullyQualifiedName] = entitySet;
}
for (const singleton of rawMetadata.schema.singletons) {
objectMap[singleton.fullyQualifiedName] = singleton;
}
for (const action of rawMetadata.schema.actions) {
objectMap[action.fullyQualifiedName] = action;
if (action.isBound) {
const [actionName, actionBinding] = splitAtFirst(action.fullyQualifiedName, '(');
if (!objectMap[actionName]) {
objectMap[actionName] = {
_type: 'UnboundGenericAction',
actions: []
};
}
objectMap[actionName].actions.push(action);
const type = substringBeforeFirst(actionBinding, ')');
objectMap[`${type}/${actionName}`] = action;
} else if (!action.fullyQualifiedName.includes('()')) {
// unbound action - add empty parentheses at the end
objectMap[`${action.fullyQualifiedName}()`] = action;
}
for (const parameter of action.parameters) {
objectMap[parameter.fullyQualifiedName] = parameter;
const pathSegments = path.split('/').reduce((targetPath, segment) => {
if (segment.includes('@')) {
// Separate out the annotation
const [pathPart, annotationPart] = splitAtFirst(segment, '@');
targetPath.push(pathPart);
targetPath.push(`@${annotationPart}`);
} else {
targetPath.push(segment);
}
}
for (const actionImport of rawMetadata.schema.actionImports) {
objectMap[actionImport.fullyQualifiedName] = actionImport;
}
for (const complexType of rawMetadata.schema.complexTypes) {
objectMap[complexType.fullyQualifiedName] = complexType;
for (const property of complexType.properties) {
objectMap[property.fullyQualifiedName] = property;
}
}
for (const typeDefinition of rawMetadata.schema.typeDefinitions) {
objectMap[typeDefinition.fullyQualifiedName] = typeDefinition;
}
for (const entityType of rawMetadata.schema.entityTypes) {
(entityType as EntityType).annotations = {}; // 'annotations' property is mandatory
objectMap[entityType.fullyQualifiedName] = entityType;
objectMap[`Collection(${entityType.fullyQualifiedName})`] = entityType;
for (const property of entityType.entityProperties) {
objectMap[property.fullyQualifiedName] = property;
// Handle complex types
const complexTypeDefinition = objectMap[property.type] as ComplexType | TypeDefinition;
if (isComplexTypeDefinition(complexTypeDefinition)) {
for (const complexTypeProp of complexTypeDefinition.properties) {
const complexTypePropTarget: RawProperty = Object.assign(complexTypeProp, {
_type: 'Property',
fullyQualifiedName: property.fullyQualifiedName + '/' + complexTypeProp.name
});
objectMap[complexTypePropTarget.fullyQualifiedName] = complexTypePropTarget;
}
}
}
for (const navProperty of entityType.navigationProperties) {
objectMap[navProperty.fullyQualifiedName] = navProperty;
}
}
return targetPath;
}, [] as string[]);
for (const annotationSource of Object.keys(rawMetadata.schema.annotations)) {
for (const annotationList of rawMetadata.schema.annotations[annotationSource]) {
const currentTargetName = unalias(rawMetadata.references, annotationList.target);
annotationList.annotations.forEach((annotation) => {
let annotationFQN = `${currentTargetName}@${unalias(rawMetadata.references, annotation.term)}`;
if (annotation.qualifier) {
annotationFQN += `#${annotation.qualifier}`;
}
objectMap[annotationFQN] = annotation;
(annotation as Annotation).fullyQualifiedName = annotationFQN;
});
}
// determine the starting point for the resolution
if (startElement === undefined) {
// no starting point given: start at the entity container
startElement = converter.getConvertedEntityContainer();
} else if (startElement[ANNOTATION_TARGET] !== undefined) {
// annotation: start at the annotation target
startElement = startElement[ANNOTATION_TARGET];
} else if (startElement._type === 'Property') {
// property: start at the entity type the property belongs to
startElement = converter.getConvertedEntityType(substringBeforeFirst(startElement.fullyQualifiedName, '/'));
}
return objectMap;
}
/**
* Combine two strings representing path in the metamodel while ensuring their specificities (annotation...) are respected.
*
* @param currentTarget the current path
* @param path the part we want to append
* @returns the complete path including the extension.
*/
function combinePath(currentTarget: string, path: string): string {
if (path.startsWith('@')) {
return currentTarget + unalias(defaultReferences, path);
} else {
return currentTarget + '/' + path;
}
}
const result = pathSegments.reduce(
(current: ResolutionTarget<any>, segment: string) => {
const error = (message: string) => {
current.messages.push({ message });
current.objectPath = appendObjectPath(current.objectPath, undefined);
current.target = undefined;
return current;
};
const ALL_ANNOTATION_ERRORS: any = {};
let ANNOTATION_ERRORS: { message: string }[] = [];
if (current.target === undefined) {
return current;
}
/**
* @param path
* @param oErrorMsg
*/
function addAnnotationErrorMessage(path: string, oErrorMsg: any) {
if (!ALL_ANNOTATION_ERRORS[path]) {
ALL_ANNOTATION_ERRORS[path] = [oErrorMsg];
} else {
ALL_ANNOTATION_ERRORS[path].push(oErrorMsg);
}
}
current.objectPath = appendObjectPath(current.objectPath, current.target);
/**
* Resolves a specific path based on the objectMap.
*
* @param objectMap
* @param currentTarget
* @param path
* @param pathOnly
* @param includeVisitedObjects
* @param annotationsTerm
* @returns the resolved object
*/
function _resolveTarget(
objectMap: any,
currentTarget: any,
path: string,
pathOnly: boolean = false,
includeVisitedObjects: boolean = false,
annotationsTerm?: string
) {
let oErrorMsg;
if (!path) {
return undefined;
}
const aVisitedObjects: any[] = [];
if (currentTarget && currentTarget._type === 'Property') {
currentTarget = objectMap[substringBeforeFirst(currentTarget.fullyQualifiedName, '/')];
}
path = combinePath(currentTarget.fullyQualifiedName, path);
// Annotation
if (segment.startsWith('@') && segment !== '@$ui5.overload') {
const [vocabularyAlias, term] = converter.splitTerm(segment);
const annotation = current.target.annotations[vocabularyAlias.substring(1)]?.[term];
const pathSplit = path.split('/');
const targetPathSplit: string[] = [];
pathSplit.forEach((pathPart) => {
// Separate out the annotation
if (pathPart.includes('@')) {
const [splittedPath, annotationPath] = splitAtFirst(pathPart, '@');
targetPathSplit.push(splittedPath);
targetPathSplit.push(`@${annotationPath}`);
} else {
targetPathSplit.push(pathPart);
}
});
let currentPath = path;
let currentContext = currentTarget;
const target = targetPathSplit.reduce((currentValue: any, pathPart) => {
if (pathPart === '$Type' && currentValue._type === 'EntityType') {
return currentValue;
}
if (pathPart === '$' && currentValue._type === 'EntitySet') {
return currentValue;
}
if ((pathPart === '@$ui5.overload' || pathPart === '0') && currentValue._type === 'Action') {
return currentValue;
}
if (pathPart.length === 0) {
// Empty Path after an entitySet means entityType
if (
currentValue &&
(currentValue._type === 'EntitySet' || currentValue._type === 'Singleton') &&
currentValue.entityType
) {
if (includeVisitedObjects) {
aVisitedObjects.push(currentValue);
if (annotation !== undefined) {
current.target = annotation;
return current;
}
currentValue = currentValue.entityType;
return error(
`Annotation '${segment.substring(1)}' not found on ${current.target._type} '${
current.target.fullyQualifiedName
}'`
);
}
if (currentValue && currentValue._type === 'NavigationProperty' && currentValue.targetType) {
if (includeVisitedObjects) {
aVisitedObjects.push(currentValue);
// $Path / $AnnotationPath syntax
if (current.target.$target) {
let subPath: string | undefined;
if (segment === '$AnnotationPath') {
subPath = current.target.value;
} else if (segment === '$Path') {
subPath = current.target.path;
}
currentValue = currentValue.targetType;
if (subPath !== undefined) {
const subTarget = resolveTarget(converter, current.target[ANNOTATION_TARGET], subPath);
subTarget.objectPath.forEach((visitedSubObject: any) => {
if (!current.objectPath.includes(visitedSubObject)) {
current.objectPath = appendObjectPath(current.objectPath, visitedSubObject);
}
});
current.target = subTarget.target;
current.objectPath = appendObjectPath(current.objectPath, current.target);
return current;
}
}
return currentValue;
}
if (includeVisitedObjects && currentValue !== null && currentValue !== undefined) {
aVisitedObjects.push(currentValue);
}
if (!currentValue) {
currentPath = pathPart;
} else if ((currentValue._type === 'EntitySet' || currentValue._type === 'Singleton') && pathPart === '$Type') {
currentValue = currentValue.targetType;
return currentValue;
} else if (
(currentValue._type === 'EntitySet' || currentValue._type === 'Singleton') &&
pathPart === '$NavigationPropertyBinding'
) {
currentValue = currentValue.navigationPropertyBinding;
return currentValue;
} else if (
(currentValue._type === 'EntitySet' || currentValue._type === 'Singleton') &&
currentValue.entityType
) {
currentPath = combinePath(currentValue.entityTypeName, pathPart);
} else if (currentValue._type === 'NavigationProperty') {
currentPath = combinePath(currentValue.fullyQualifiedName, pathPart);
if (!objectMap[currentPath]) {
// Fallback log error
currentPath = combinePath(currentValue.targetTypeName, pathPart);
}
} else if (currentValue._type === 'Property') {
// ComplexType or Property
if (currentValue.targetType) {
currentPath = combinePath(currentValue.targetType.fullyQualifiedName, pathPart);
} else {
currentPath = combinePath(currentValue.fullyQualifiedName, pathPart);
}
} else if (currentValue._type === 'Action' && currentValue.isBound) {
currentPath = combinePath(currentValue.fullyQualifiedName, pathPart);
if (pathPart === '$Parameter') {
return currentValue.parameters;
}
if (!objectMap[currentPath]) {
currentPath = combinePath(currentValue.sourceType, pathPart);
}
} else if (currentValue._type === 'ActionParameter') {
currentPath = combinePath(substringBeforeLast(currentTarget.fullyQualifiedName, '/'), pathPart);
if (!objectMap[currentPath]) {
currentPath = combinePath(
(objectMap[substringBeforeLast(currentTarget.fullyQualifiedName, '/')] as Action).sourceType,
pathPart
);
}
} else {
currentPath = combinePath(currentValue.fullyQualifiedName, pathPart);
if (pathPart !== 'name' && currentValue[pathPart] !== undefined) {
return currentValue[pathPart];
} else if (pathPart === '$AnnotationPath' && currentValue.$target) {
const contextToResolve = objectMap[substringBeforeFirst(currentValue.fullyQualifiedName, '@')];
const subTarget: any = _resolveTarget(objectMap, contextToResolve, currentValue.value, false, true);
subTarget.visitedObjects.forEach((visitedSubObject: any) => {
if (!aVisitedObjects.includes(visitedSubObject)) {
aVisitedObjects.push(visitedSubObject);
// traverse based on the element type
switch (current.target?._type) {
case 'EntityContainer':
{
const thisElement = current.target as EntityContainer;
if (segment === '' || segment === thisElement.fullyQualifiedName) {
return current;
}
// next element: EntitySet, Singleton or ActionImport?
const nextElement: EntitySet | Singleton | ActionImport | undefined =
thisElement.entitySets.by_name(segment) ??
thisElement.singletons.by_name(segment) ??
thisElement.actionImports.by_name(segment);
if (nextElement) {
current.target = nextElement;
return current;
}
}
});
return subTarget.target;
} else if (pathPart === '$Path' && currentValue.$target) {
currentContext = aVisitedObjects
.concat()
.reverse()
.find(
(obj) =>
obj._type === 'EntityType' ||
obj._type === 'EntitySet' ||
obj._type === 'Singleton' ||
obj._type === 'NavigationProperty'
);
if (currentContext) {
const subTarget: any = _resolveTarget(objectMap, currentContext, currentValue.path, false, true);
subTarget.visitedObjects.forEach((visitedSubObject: any) => {
if (!aVisitedObjects.includes(visitedSubObject)) {
aVisitedObjects.push(visitedSubObject);
break;
case 'EntitySet':
case 'Singleton': {
const thisElement = current.target as EntitySet | Singleton;
if (segment === '' || segment === '$Type') {
// Empty Path after an EntitySet or Singleton means EntityType
current.target = thisElement.entityType;
return current;
}
if (segment === '$') {
return current;
}
if (segment === '$NavigationPropertyBinding') {
const navigationPropertyBindings = thisElement.navigationPropertyBinding;
current.target = navigationPropertyBindings;
return current;
}
// continue resolving at the EntitySet's or Singleton's type
const result = resolveTarget(converter, thisElement.entityType, segment);
current.target = result.target;
current.objectPath = result.objectPath.reduce(appendObjectPath, current.objectPath);
return current;
}
case 'EntityType':
{
const thisElement = current.target as EntityType;
if (segment === '' || segment === '$Type') {
return current;
}
});
return subTarget.target;
const property = thisElement.entityProperties.by_name(segment);
if (property) {
current.target = property;
return current;
}
const navigationProperty = thisElement.navigationProperties.by_name(segment);
if (navigationProperty) {
current.target = navigationProperty;
return current;
}
const action = thisElement.actions[segment];
if (action) {
current.target = action;
return current;
}
}
break;
case 'ActionImport': {
// continue resolving at the Action
const result = resolveTarget(converter, current.target.action, segment);
current.target = result.target;
current.objectPath = result.objectPath.reduce(appendObjectPath, current.objectPath);
return current;
}
return currentValue.$target;
} else if (pathPart.startsWith('$Path') && currentValue.$target) {
const intermediateTarget = currentValue.$target;
currentPath = combinePath(intermediateTarget.fullyQualifiedName, pathPart.substring(5));
} else if (currentValue.hasOwnProperty('$Type') && !objectMap[currentPath]) {
// This is now an annotation value
const entityType = objectMap[substringBeforeFirst(currentValue.fullyQualifiedName, '@')];
if (entityType) {
currentPath = combinePath(entityType.fullyQualifiedName, pathPart);
case 'Action': {
const thisElement = current.target as Action;
if (segment === '') {
return current;
}
if (segment === '@$ui5.overload' || segment === '0') {
return current;
}
if (segment === '$Parameter' && thisElement.isBound) {
current.target = thisElement.parameters;
return current;
}
const nextElement =
thisElement.parameters[segment as any] ??
thisElement.parameters.find((param: ActionParameter) => param.name === segment);
if (nextElement) {
current.target = nextElement;
return current;
}
break;
}
case 'Property':
{
const thisElement = current.target as Property;
// Property or NavigationProperty of the ComplexType
const type = thisElement.targetType as ComplexType | undefined;
if (type !== undefined) {
const property = type.properties.by_name(segment);
if (property) {
current.target = property;
return current;
}
const navigationProperty = type.navigationProperties.by_name(segment);
if (navigationProperty) {
current.target = navigationProperty;
return current;
}
}
}
break;
case 'ActionParameter':
const referencedType = (current.target as ActionParameter).typeReference;
if (referencedType !== undefined) {
const result = resolveTarget(converter, referencedType, segment);
current.target = result.target;
current.objectPath = result.objectPath.reduce(appendObjectPath, current.objectPath);
return current;
}
break;
case 'NavigationProperty':
// continue at the NavigationProperty's target type
const result = resolveTarget(converter, (current.target as NavigationProperty).targetType, segment);
current.target = result.target;
current.objectPath = result.objectPath.reduce(appendObjectPath, current.objectPath);
return current;
default:
if (current.target[segment]) {
current.target = current.target[segment];
current.objectPath = appendObjectPath(current.objectPath, current.target);
}
return current;
}
}
return objectMap[currentPath];
}, null);
if (!target) {
return error(
`Element '${segment}' not found at ${current.target._type} '${current.target.fullyQualifiedName}'`
);
},
{ target: startElement, objectPath: [], messages: [] }
);
// Diagnostics
result.messages.forEach((message) => converter.logError(message.message));
if (!result.target) {
if (annotationsTerm) {
const annotationType = inferTypeFromTerm(annotationsTerm, currentTarget);
oErrorMsg = {
message:
'Unable to resolve the path expression: ' +
const annotationType = inferTypeFromTerm(converter, annotationsTerm, startElement.fullyQualifiedName);
converter.logError(
'Unable to resolve the path expression: ' +
'\n' +

@@ -387,8 +368,6 @@ path +

'>'
};
addAnnotationErrorMessage(path, oErrorMsg);
);
} else {
oErrorMsg = {
message:
'Unable to resolve the path expression: ' +
converter.logError(
'Unable to resolve the path expression: ' +
path +

@@ -399,22 +378,13 @@ '\n' +

'<Annotation Term = ' +
pathSplit[0] +
pathSegments[0] +
'>' +
'\n' +
'<PropertyValue Path= ' +
pathSplit[1] +
pathSegments[1] +
'>'
};
addAnnotationErrorMessage(path, oErrorMsg);
);
}
}
if (pathOnly) {
return currentPath;
}
if (includeVisitedObjects) {
return {
visitedObjects: aVisitedObjects,
target: target
};
}
return target;
return result;
}

@@ -432,3 +402,11 @@

function parseValue(propertyValue: Expression, valueFQN: string, objectMap: any, context: ConversionContext) {
function parseValue(
converter: Converter,
currentTarget: any,
currentTerm: string,
currentProperty: string,
currentSource: string,
propertyValue: Expression,
valueFQN: string
) {
if (propertyValue === undefined) {

@@ -449,3 +427,3 @@ return undefined;

case 'EnumMember':
const aliasedEnum = alias(context.rawMetadata.references, propertyValue.EnumMember);
const aliasedEnum = converter.alias(propertyValue.EnumMember);
const splitEnum = aliasedEnum.split(' ');

@@ -462,10 +440,4 @@ if (splitEnum[0] && EnumIsFlag[substringBeforeFirst(splitEnum[0], '/')]) {

fullyQualifiedName: valueFQN,
$target: _resolveTarget(
objectMap,
context.currentTarget,
propertyValue.PropertyPath,
false,
false,
context.currentTerm
)
$target: resolveTarget(converter, currentTarget, propertyValue.PropertyPath, currentTerm).target,
[ANNOTATION_TARGET]: currentTarget
};

@@ -477,51 +449,57 @@ case 'NavigationPropertyPath':

fullyQualifiedName: valueFQN,
$target: _resolveTarget(
objectMap,
context.currentTarget,
propertyValue.NavigationPropertyPath,
false,
false,
context.currentTerm
)
$target: resolveTarget(converter, currentTarget, propertyValue.NavigationPropertyPath, currentTerm)
.target,
[ANNOTATION_TARGET]: currentTarget
};
case 'AnnotationPath':
const annotationTarget = _resolveTarget(
objectMap,
context.currentTarget,
unalias(context.rawMetadata.references, propertyValue.AnnotationPath) as string,
true,
false,
context.currentTerm
);
const annotationPath = {
return {
type: 'AnnotationPath',
value: propertyValue.AnnotationPath,
fullyQualifiedName: valueFQN,
$target: annotationTarget,
annotationsTerm: context.currentTerm,
$target: resolveTarget(
converter,
currentTarget,
converter.unalias(propertyValue.AnnotationPath),
currentTerm
).target,
annotationsTerm: currentTerm,
term: '',
path: ''
path: '',
[ANNOTATION_TARGET]: currentTarget
};
context.unresolvedAnnotations.push({ inline: false, toResolve: annotationPath });
return annotationPath;
case 'Path':
const $target = _resolveTarget(
objectMap,
context.currentTarget,
propertyValue.Path,
true,
false,
context.currentTerm
);
const path = new Path(propertyValue, $target, context.currentTerm, '');
context.unresolvedAnnotations.push({
inline: isAnnotationPath(propertyValue.Path),
toResolve: path
});
return path;
const $target = resolveTarget(converter, currentTarget, propertyValue.Path, currentTerm).target;
if (isAnnotationPath(propertyValue.Path)) {
// inline the target
return $target;
} else {
return {
type: 'Path',
path: propertyValue.Path,
fullyQualifiedName: valueFQN,
$target: $target,
[ANNOTATION_TARGET]: currentTarget
};
}
case 'Record':
return parseRecord(propertyValue.Record, valueFQN, objectMap, context);
return parseRecord(
converter,
currentTerm,
currentTarget,
currentProperty,
currentSource,
propertyValue.Record,
valueFQN
);
case 'Collection':
return parseCollection(propertyValue.Collection, valueFQN, objectMap, context);
return parseCollection(
converter,
currentTarget,
currentTerm,
currentProperty,
currentSource,
propertyValue.Collection,
valueFQN
);
case 'Apply':

@@ -547,8 +525,14 @@ case 'Null':

*
* @param annotationsTerm The annotation term
* @param annotationTarget the annotation target
* @param currentProperty the current property of the record
* @returns the inferred type.
* @param converter Converter
* @param annotationsTerm The annotation term
* @param annotationTarget The annotation target
* @param currentProperty The current property of the record
* @returns The inferred type.
*/
function inferTypeFromTerm(annotationsTerm: string, annotationTarget: string, currentProperty?: string) {
function inferTypeFromTerm(
converter: Converter,
annotationsTerm: string,
annotationTarget: string,
currentProperty?: string
) {
let targetType = (TermToTypes as any)[annotationsTerm];

@@ -559,5 +543,5 @@ if (currentProperty) {

}
const oErrorMsg = {
isError: false,
message: `The type of the record used within the term ${annotationsTerm} was not defined and was inferred as ${targetType}.
converter.logError(
`The type of the record used within the term ${annotationsTerm} was not defined and was inferred as ${targetType}.
Hint: If possible, try to maintain the Type property for each Record.

@@ -569,25 +553,27 @@ <Annotations Target="${annotationTarget}">

</Annotations>`
};
addAnnotationErrorMessage(annotationTarget + '/' + annotationsTerm, oErrorMsg);
);
return targetType;
}
function isDataFieldWithForAction(annotationContent: any, annotationTerm: any) {
function isDataFieldWithForAction(annotationContent: any) {
return (
annotationContent.hasOwnProperty('Action') &&
(annotationTerm.$Type === 'com.sap.vocabularies.UI.v1.DataFieldForAction' ||
annotationTerm.$Type === 'com.sap.vocabularies.UI.v1.DataFieldWithAction')
(annotationContent.$Type === 'com.sap.vocabularies.UI.v1.DataFieldForAction' ||
annotationContent.$Type === 'com.sap.vocabularies.UI.v1.DataFieldWithAction')
);
}
function parseRecordType(recordDefinition: AnnotationRecord, context: ConversionContext) {
function parseRecordType(
converter: Converter,
currentTerm: string,
currentTarget: any,
currentProperty: string | undefined,
recordDefinition: AnnotationRecord
) {
let targetType;
if (!recordDefinition.type && context.currentTerm) {
targetType = inferTypeFromTerm(
context.currentTerm,
context.currentTarget.fullyQualifiedName,
context.currentProperty
);
if (!recordDefinition.type && currentTerm) {
targetType = inferTypeFromTerm(converter, currentTerm, currentTarget.fullyQualifiedName, currentProperty);
} else {
targetType = unalias(context.rawMetadata.references, recordDefinition.type);
targetType = converter.unalias(recordDefinition.type);
}

@@ -598,70 +584,80 @@ return targetType;

function parseRecord(
recordDefinition: AnnotationRecord,
currentFQN: string,
objectMap: any,
context: ConversionContext
converter: Converter,
currentTerm: string,
currentTarget: any,
currentProperty: string | undefined,
currentSource: string,
annotationRecord: AnnotationRecord,
currentFQN: string
) {
const targetType = parseRecordType(recordDefinition, context);
const annotationTerm: any = {
$Type: targetType,
$Type: parseRecordType(converter, currentTerm, currentTarget, currentProperty, annotationRecord),
fullyQualifiedName: currentFQN,
annotations: {}
[ANNOTATION_TARGET]: currentTarget
};
const annotationContent: any = {};
if (Array.isArray(recordDefinition.annotations)) {
const subAnnotationList = {
target: currentFQN,
annotations: recordDefinition.annotations,
__source: context.currentSource
};
context.additionalAnnotations.push(subAnnotationList);
}
if (recordDefinition.propertyValues) {
recordDefinition.propertyValues.forEach((propertyValue: PropertyValue) => {
context.currentProperty = propertyValue.name;
annotationContent[propertyValue.name] = parseValue(
// annotations on the record
lazy(annotationTerm, 'annotations', () => {
// be graceful when resolving annotations on annotations: Sometimes they are referenced directly, sometimes they
// are part of the global annotations list
let annotations;
if (annotationRecord.annotations && annotationRecord.annotations.length > 0) {
annotations = annotationRecord.annotations;
} else {
annotations = converter.rawAnnotationsPerTarget[currentFQN]?.annotations;
}
annotations?.forEach((annotation: any) => {
annotation.target = currentFQN;
annotation.__source = currentSource;
annotation[ANNOTATION_TARGET] = currentTarget;
annotation.fullyQualifiedName = `${currentFQN}@${annotation.term}`;
});
return createAnnotationsObject(converter, annotationTerm, annotations ?? []);
});
const annotationContent = annotationRecord.propertyValues?.reduce((annotationContent, propertyValue) => {
lazy(annotationContent, propertyValue.name, () =>
parseValue(
converter,
currentTarget,
currentTerm,
propertyValue.name,
currentSource,
propertyValue.value,
`${currentFQN}/${propertyValue.name}`,
objectMap,
context
);
if (Array.isArray(propertyValue.annotations)) {
const subAnnotationList = {
target: `${currentFQN}/${propertyValue.name}`,
annotations: propertyValue.annotations,
__source: context.currentSource
};
context.additionalAnnotations.push(subAnnotationList);
`${currentFQN}/${propertyValue.name}`
)
);
return annotationContent;
}, annotationTerm);
if (isDataFieldWithForAction(annotationContent)) {
lazy(annotationContent, 'ActionTarget', () => {
// try to resolve to a bound action of the annotation target
let actionTarget = currentTarget.actions?.[annotationContent.Action];
if (!actionTarget) {
// try to find a corresponding unbound action
actionTarget = converter.getConvertedActionImport(annotationContent.Action)?.action;
}
if (isDataFieldWithForAction(annotationContent, annotationTerm)) {
// try to resolve to a bound action of the annotation target
annotationContent.ActionTarget = context.currentTarget.actions?.[annotationContent.Action];
if (!annotationContent.ActionTarget) {
const action = objectMap[annotationContent.Action];
if (action?.isBound) {
// bound action of a different entity type
annotationContent.ActionTarget = action;
} else if (action) {
// unbound action --> resolve via the action import
annotationContent.ActionTarget = action.action;
}
if (!actionTarget) {
// try to find a corresponding bound (!) action
actionTarget = converter.getConvertedAction(annotationContent.Action);
if (!actionTarget?.isBound) {
actionTarget = undefined;
}
}
if (!annotationContent.ActionTarget) {
// Add to diagnostics debugger;
ANNOTATION_ERRORS.push({
message:
'Unable to resolve the action ' +
annotationContent.Action +
' defined for ' +
annotationTerm.fullyQualifiedName
});
}
if (!actionTarget) {
converter.logError(
`Unable to resolve the action '${annotationContent.Action}' defined for '${annotationTerm.fullyQualifiedName}'`
);
}
return actionTarget;
});
context.currentProperty = undefined;
}
return Object.assign(annotationTerm, annotationContent);
return annotationContent;
}

@@ -722,83 +718,91 @@

function parseCollection(collectionDefinition: any[], parentFQN: string, objectMap: any, context: ConversionContext) {
function parseCollection(
converter: Converter,
currentTarget: any,
currentTerm: string,
currentProperty: string,
currentSource: string,
collectionDefinition: any[],
parentFQN: string
) {
const collectionDefinitionType = getOrInferCollectionType(collectionDefinition);
switch (collectionDefinitionType) {
case 'PropertyPath':
return collectionDefinition.map((propertyPath, propertyIdx) => {
return {
return collectionDefinition.map((propertyPath, propertyIdx): PropertyPath => {
const result: PropertyPath = {
type: 'PropertyPath',
value: propertyPath.PropertyPath,
fullyQualifiedName: `${parentFQN}/${propertyIdx}`,
$target: _resolveTarget(
objectMap,
context.currentTarget,
propertyPath.PropertyPath,
false,
false,
context.currentTerm
)
};
fullyQualifiedName: `${parentFQN}/${propertyIdx}`
} as any;
lazy(
result,
'$target',
() =>
resolveTarget<Property>(converter, currentTarget, propertyPath.PropertyPath, currentTerm)
.target ?? ({} as Property) // TODO: $target is mandatory - throw an error?
);
return result;
});
case 'Path':
// TODO: make lazy?
return collectionDefinition.map((pathValue) => {
const $target = _resolveTarget(
objectMap,
context.currentTarget,
pathValue.Path,
true,
false,
context.currentTerm
);
const path = new Path(pathValue, $target, context.currentTerm, '');
context.unresolvedAnnotations.push({
inline: isAnnotationPath(pathValue.Path),
toResolve: path
});
return path;
return resolveTarget(converter, currentTarget, pathValue.Path, currentTerm).target;
});
case 'AnnotationPath':
return collectionDefinition.map((annotationPath, annotationIdx) => {
const annotationTarget = _resolveTarget(
objectMap,
context.currentTarget,
annotationPath.AnnotationPath,
true,
false,
context.currentTerm
);
const annotationCollectionElement = {
const result = {
type: 'AnnotationPath',
value: annotationPath.AnnotationPath,
fullyQualifiedName: `${parentFQN}/${annotationIdx}`,
$target: annotationTarget,
annotationsTerm: context.currentTerm,
annotationsTerm: currentTerm,
term: '',
path: ''
};
context.unresolvedAnnotations.push({
inline: false,
toResolve: annotationCollectionElement
});
return annotationCollectionElement;
} as any;
lazy(
result,
'$target',
() => resolveTarget(converter, currentTarget, annotationPath.AnnotationPath, currentTerm).target
);
return result;
});
case 'NavigationPropertyPath':
return collectionDefinition.map((navPropertyPath, navPropIdx) => {
return {
const result = {
type: 'NavigationPropertyPath',
value: navPropertyPath.NavigationPropertyPath,
fullyQualifiedName: `${parentFQN}/${navPropIdx}`,
$target: _resolveTarget(
objectMap,
context.currentTarget,
navPropertyPath.NavigationPropertyPath,
false,
false,
context.currentTerm
)
};
fullyQualifiedName: `${parentFQN}/${navPropIdx}`
} as any;
lazy(
result,
'$target',
() =>
resolveTarget(converter, currentTarget, navPropertyPath.NavigationPropertyPath, currentTerm)
.target
);
return result;
});
case 'Record':
return collectionDefinition.map((recordDefinition, recordIdx) => {
return parseRecord(recordDefinition, `${parentFQN}/${recordIdx}`, objectMap, context);
return parseRecord(
converter,
currentTerm,
currentTarget,
currentProperty,
currentSource,
recordDefinition,
`${parentFQN}/${recordIdx}`
);
});
case 'Apply':

@@ -816,11 +820,8 @@ case 'Null':

case 'Or':
return collectionDefinition.map((ifValue) => {
return ifValue;
});
return collectionDefinition.map((ifValue) => ifValue);
case 'String':
return collectionDefinition.map((stringValue) => {
if (typeof stringValue === 'string') {
if (typeof stringValue === 'string' || stringValue === undefined) {
return stringValue;
} else if (stringValue === undefined) {
return stringValue;
} else {

@@ -830,2 +831,3 @@ return stringValue.String;

});
default:

@@ -839,581 +841,703 @@ if (collectionDefinition.length === 0) {

type Resolveable = {
inline: boolean;
toResolve: {
$target: string;
targetString?: string;
annotationsTerm?: string;
annotationType?: string;
term: string;
path: string;
};
};
function isV4NavigationProperty(
navProp: RawV2NavigationProperty | RawV4NavigationProperty
): navProp is RawV4NavigationProperty {
return !!(navProp as BaseNavigationProperty).targetTypeName;
}
function convertAnnotation(annotation: Annotation, objectMap: any, context: ConversionContext): any {
if (annotation.record) {
return parseRecord(annotation.record, annotation.fullyQualifiedName, objectMap, context);
} else if (annotation.collection === undefined) {
if (annotation.value) {
return parseValue(annotation.value, annotation.fullyQualifiedName, objectMap, context);
} else {
return true;
}
} else if (annotation.collection) {
const collection: any = parseCollection(
annotation.collection,
annotation.fullyQualifiedName,
objectMap,
context
/**
* Split the alias from the term value.
*
* @param references the current set of references
* @param termValue the value of the term
* @returns the term alias and the actual term value
*/
function splitTerm(references: ReferencesWithMap, termValue: string) {
return splitAtLast(alias(references, termValue), '.');
}
function convertAnnotation(converter: Converter, target: any, rawAnnotation: RawAnnotation): Annotation {
let annotation: any;
if (rawAnnotation.record) {
annotation = parseRecord(
converter,
rawAnnotation.term,
target,
'',
(rawAnnotation as any).__source,
rawAnnotation.record,
(rawAnnotation as any).fullyQualifiedName
);
collection.fullyQualifiedName = annotation.fullyQualifiedName;
return collection;
} else if (rawAnnotation.collection === undefined) {
annotation = parseValue(
converter,
target,
rawAnnotation.term,
'',
(rawAnnotation as any).__source,
rawAnnotation.value ?? { type: 'Bool', Bool: true },
(rawAnnotation as any).fullyQualifiedName
);
} else if (rawAnnotation.collection) {
annotation = parseCollection(
converter,
target,
rawAnnotation.term,
'',
(rawAnnotation as any).__source,
rawAnnotation.collection,
(rawAnnotation as any).fullyQualifiedName
);
} else {
throw new Error('Unsupported case');
}
switch (typeof annotation) {
case 'string':
// eslint-disable-next-line no-new-wrappers
annotation = new String(annotation);
break;
case 'boolean':
// eslint-disable-next-line no-new-wrappers
annotation = new Boolean(annotation);
break;
case 'number':
annotation = new Number(annotation);
break;
default:
// do nothing
break;
}
annotation.fullyQualifiedName = (rawAnnotation as any).fullyQualifiedName;
annotation[ANNOTATION_TARGET] = target;
const [vocAlias, vocTerm] = converter.splitTerm(rawAnnotation.term);
annotation.term = converter.unalias(`${vocAlias}.${vocTerm}`);
annotation.qualifier = rawAnnotation.qualifier;
annotation.__source = (rawAnnotation as any).__source;
try {
lazy(annotation, 'annotations', () => {
const annotationFQN = annotation.fullyQualifiedName;
// be graceful when resolving annotations on annotations: Sometimes they are referenced directly, sometimes they
// are part of the global annotations list
let annotations;
if (rawAnnotation.annotations && rawAnnotation.annotations.length > 0) {
annotations = rawAnnotation.annotations;
} else {
annotations = converter.rawAnnotationsPerTarget[annotationFQN]?.annotations;
}
annotations?.forEach((rawSubAnnotation: any) => {
rawSubAnnotation.target = annotationFQN;
rawSubAnnotation.__source = annotation.__source;
rawSubAnnotation[ANNOTATION_TARGET] = target;
rawSubAnnotation.fullyQualifiedName = `${annotationFQN}@${rawSubAnnotation.term}`;
});
return createAnnotationsObject(converter, annotation, annotations ?? []);
});
} catch (e) {
// not an error: parseRecord() already adds annotations, but the other parseXXX functions don't, so this can happen
}
return annotation as Annotation;
}
function getAnnotationFQN(currentTargetName: string, references: Reference[], annotation: RawAnnotation) {
const annotationFQN = `${currentTargetName}@${unalias(references, annotation.term)}`;
if (annotation.qualifier) {
return `${annotationFQN}#${annotation.qualifier}`;
} else {
return annotationFQN;
}
}
/**
* Creates a resolvePath function for a given entityType.
* Merge annotation from different source together by overwriting at the term level.
*
* @param entityType The entityType for which the function should be created
* @param objectMap The current objectMap
* @returns the resolvePath function that starts at the entityType
* @param rawMetadata
* @returns the resulting merged annotations
*/
function createResolvePathFn(entityType: EntityType, objectMap: Record<string, any>) {
return function (relativePath: string, includeVisitedObjects: boolean): any {
const annotationTerm: string = '';
return _resolveTarget(objectMap, entityType, relativePath, false, includeVisitedObjects, annotationTerm);
};
function mergeAnnotations(rawMetadata: RawMetadata): Record<string, AnnotationList> {
const annotationListPerTarget: Record<string, AnnotationList> = {};
Object.keys(rawMetadata.schema.annotations).forEach((annotationSource) => {
rawMetadata.schema.annotations[annotationSource].forEach((annotationList: AnnotationList) => {
const currentTargetName = unalias(rawMetadata.references, annotationList.target) as string;
(annotationList as any).__source = annotationSource;
if (!annotationListPerTarget[currentTargetName]) {
annotationListPerTarget[currentTargetName] = {
annotations: annotationList.annotations.map((annotation: RawAnnotation) => {
(annotation as Annotation).fullyQualifiedName = getAnnotationFQN(
currentTargetName,
rawMetadata.references,
annotation
);
(annotation as any).__source = annotationSource;
return annotation;
}),
target: currentTargetName
};
(annotationListPerTarget[currentTargetName] as any).__source = annotationSource;
} else {
annotationList.annotations.forEach((annotation: RawAnnotation) => {
const findIndex = annotationListPerTarget[currentTargetName].annotations.findIndex(
(referenceAnnotation: RawAnnotation) => {
return (
referenceAnnotation.term === annotation.term &&
referenceAnnotation.qualifier === annotation.qualifier
);
}
);
(annotation as any).__source = annotationSource;
(annotation as Annotation).fullyQualifiedName = getAnnotationFQN(
currentTargetName,
rawMetadata.references,
annotation
);
if (findIndex !== -1) {
annotationListPerTarget[currentTargetName].annotations.splice(findIndex, 1, annotation);
} else {
annotationListPerTarget[currentTargetName].annotations.push(annotation);
}
});
}
});
});
return annotationListPerTarget;
}
function resolveV2NavigationProperty(
navProp: RawV2NavigationProperty,
associations: RawAssociation[],
objectMap: Record<string, any>,
outNavProp: NavigationProperty
): void {
const targetAssociation = associations.find(
(association) => association.fullyQualifiedName === navProp.relationship
);
if (targetAssociation) {
const associationEnd = targetAssociation.associationEnd.find((end) => end.role === navProp.toRole);
if (associationEnd) {
outNavProp.targetType = objectMap[associationEnd.type];
outNavProp.isCollection = associationEnd.multiplicity === '*';
class Converter {
private _rawAnnotationsPerTarget: Record<FullyQualifiedName, AnnotationList>;
get rawAnnotationsPerTarget(): Record<FullyQualifiedName, AnnotationList> {
if (this._rawAnnotationsPerTarget === undefined) {
this._rawAnnotationsPerTarget = mergeAnnotations(this.rawMetadata);
}
return this._rawAnnotationsPerTarget;
}
outNavProp.referentialConstraint = navProp.referentialConstraint || [];
getConvertedEntityContainer() {
return this.getConvertedElement(
this.rawMetadata.schema.entityContainer.fullyQualifiedName,
this.rawMetadata.schema.entityContainer,
convertEntityContainer
);
}
getConvertedEntitySet(fullyQualifiedName: FullyQualifiedName) {
return this.convertedOutput.entitySets.by_fullyQualifiedName(fullyQualifiedName);
}
getConvertedSingleton(fullyQualifiedName: FullyQualifiedName) {
return this.convertedOutput.singletons.by_fullyQualifiedName(fullyQualifiedName);
}
getConvertedEntityType(fullyQualifiedName: FullyQualifiedName) {
return this.convertedOutput.entityTypes.by_fullyQualifiedName(fullyQualifiedName);
}
getConvertedComplexType(fullyQualifiedName: FullyQualifiedName) {
return this.convertedOutput.complexTypes.by_fullyQualifiedName(fullyQualifiedName);
}
getConvertedTypeDefinition(fullyQualifiedName: FullyQualifiedName) {
return this.convertedOutput.typeDefinitions.by_fullyQualifiedName(fullyQualifiedName);
}
getConvertedActionImport(fullyQualifiedName: FullyQualifiedName) {
return this.convertedOutput.actionImports.by_fullyQualifiedName(fullyQualifiedName);
}
getConvertedAction(fullyQualifiedName: FullyQualifiedName) {
return this.convertedOutput.actions.by_fullyQualifiedName(fullyQualifiedName);
}
convert<Converted, Raw extends RawType<Converted>>(
rawValue: Raw,
map: (converter: Converter, raw: Raw) => Converted
): () => Converted;
convert<Converted, Raw extends RawType<Converted>, IndexProperty extends Extract<keyof Converted, string>>(
rawValue: Raw[],
map: (converter: Converter, raw: Raw) => Converted
): () => ArrayWithIndex<Converted, IndexProperty>;
convert<Converted, Raw extends RawType<Converted>, IndexProperty extends Extract<keyof Converted, string>>(
rawValue: Raw | Raw[],
map: (converter: Converter, raw: Raw) => Converted
): (() => Converted) | (() => ArrayWithIndex<Converted, IndexProperty>) {
if (Array.isArray(rawValue)) {
return () => {
const converted = rawValue.reduce((convertedElements, rawElement) => {
const convertedElement = this.getConvertedElement(
(rawElement as any).fullyQualifiedName,
rawElement,
map
);
if (convertedElement) {
convertedElements.push(convertedElement);
}
return convertedElements;
}, [] as Converted[]);
addGetByValue(converted, 'name' as any);
addGetByValue(converted, 'fullyQualifiedName' as any);
return converted as ArrayWithIndex<Converted, IndexProperty>;
};
} else {
return () => this.getConvertedElement(rawValue.fullyQualifiedName, rawValue, map)!;
}
}
private rawMetadata: RawMetadata;
private convertedElements: Map<FullyQualifiedName, any> = new Map();
private convertedOutput: ConvertedMetadata;
rawSchema: RawSchema;
constructor(rawMetadata: RawMetadata, convertedOutput: ConvertedMetadata) {
this.rawMetadata = rawMetadata;
this.rawSchema = rawMetadata.schema;
this.convertedOutput = convertedOutput;
}
getConvertedElement<ConvertedType, RawType extends RemoveAnnotationAndType<ConvertedType>>(
fullyQualifiedName: FullyQualifiedName,
rawElement: RawType | undefined | ((fullyQualifiedName: FullyQualifiedName) => RawType | undefined),
map: (converter: Converter, raw: RawType) => ConvertedType
): ConvertedType | undefined {
let converted: ConvertedType | undefined = this.convertedElements.get(fullyQualifiedName);
if (converted === undefined) {
const rawMetadata =
typeof rawElement === 'function' ? rawElement.apply(undefined, [fullyQualifiedName]) : rawElement;
if (rawMetadata !== undefined) {
converted = map.apply(undefined, [this, rawMetadata]);
this.convertedElements.set(fullyQualifiedName, converted);
}
}
return converted;
}
logError(message: string) {
this.convertedOutput.diagnostics.push({ message });
}
splitTerm(term: string) {
return splitTerm(this.rawMetadata.references, term);
}
alias(value: string) {
return alias(this.rawMetadata.references, value);
}
unalias(value: string | undefined) {
return unalias(this.rawMetadata.references, value) ?? '';
}
}
function resolveV4NavigationProperty(
navProp: RawV4NavigationProperty,
objectMap: Record<string, any>,
outNavProp: NavigationProperty
): void {
outNavProp.targetType = objectMap[navProp.targetTypeName];
outNavProp.partner = navProp.partner;
outNavProp.isCollection = navProp.isCollection;
outNavProp.containsTarget = navProp.containsTarget;
outNavProp.referentialConstraint = navProp.referentialConstraint;
type RawType<T> = RemoveAnnotationAndType<T> & { fullyQualifiedName: FullyQualifiedName };
function resolveEntityType(converter: Converter, fullyQualifiedName: FullyQualifiedName) {
return () => {
let entityType = converter.getConvertedEntityType(fullyQualifiedName);
if (!entityType) {
converter.logError(`EntityType '${fullyQualifiedName}' not found`);
entityType = {} as EntityType;
}
return entityType;
};
}
function isV4NavigationProperty(
navProp: RawV2NavigationProperty | RawV4NavigationProperty
): navProp is RawV4NavigationProperty {
return !!(navProp as BaseNavigationProperty).targetTypeName;
function resolveNavigationPropertyBindings(
converter: Converter,
rawNavigationPropertyBindings: Singleton['navigationPropertyBinding'] | EntitySet['navigationPropertyBinding'],
rawElement: RawSingleton | RawEntitySet
) {
return () =>
Object.keys(rawNavigationPropertyBindings).reduce((navigationPropertyBindings, bindingName) => {
const rawBindingTarget = rawNavigationPropertyBindings[bindingName];
lazy(navigationPropertyBindings, bindingName, () => {
let resolvedBindingTarget;
if (rawBindingTarget._type === 'Singleton') {
resolvedBindingTarget = converter.getConvertedSingleton(rawBindingTarget.fullyQualifiedName);
} else {
resolvedBindingTarget = converter.getConvertedEntitySet(rawBindingTarget.fullyQualifiedName);
}
if (!resolvedBindingTarget) {
converter.logError(
`${rawElement._type} '${rawElement.fullyQualifiedName}': Failed to resolve NavigationPropertyBinding ${bindingName}`
);
resolvedBindingTarget = {} as any;
}
return resolvedBindingTarget;
});
return navigationPropertyBindings;
}, {} as EntitySet['navigationPropertyBinding'] | Singleton['navigationPropertyBinding']);
}
function prepareNavigationProperties(
navigationProperties: (RawV4NavigationProperty | RawV2NavigationProperty)[],
associations: RawAssociation[],
objectMap: Record<string, any>
) {
return navigationProperties.map((navProp) => {
const outNavProp: NavigationProperty = {
_type: 'NavigationProperty',
name: navProp.name,
fullyQualifiedName: navProp.fullyQualifiedName,
isCollection: false,
containsTarget: false,
referentialConstraint: [],
annotations: {},
partner: '',
targetType: undefined as any,
targetTypeName: ''
};
if (isV4NavigationProperty(navProp)) {
resolveV4NavigationProperty(navProp, objectMap, outNavProp);
} else {
resolveV2NavigationProperty(navProp, associations, objectMap, outNavProp);
function resolveAnnotations(converter: Converter, rawAnnotationTarget: any) {
const nestedAnnotations = rawAnnotationTarget.annotations;
return () =>
createAnnotationsObject(
converter,
rawAnnotationTarget,
nestedAnnotations ??
converter.rawAnnotationsPerTarget[rawAnnotationTarget.fullyQualifiedName]?.annotations ??
[]
);
}
function createAnnotationsObject(converter: Converter, target: any, rawAnnotations: RawAnnotation[]) {
return rawAnnotations.reduce((vocabularyAliases, annotation) => {
const [vocAlias, vocTerm] = converter.splitTerm(annotation.term);
const vocTermWithQualifier = `${vocTerm}${annotation.qualifier ? '#' + annotation.qualifier : ''}`;
if (vocabularyAliases[vocAlias] === undefined) {
vocabularyAliases[vocAlias] = {};
}
if (outNavProp.targetType) {
outNavProp.targetTypeName = outNavProp.targetType.fullyQualifiedName;
if (!vocabularyAliases[vocAlias].hasOwnProperty(vocTermWithQualifier)) {
lazy(vocabularyAliases[vocAlias], vocTermWithQualifier, () =>
converter.getConvertedElement(
(annotation as Annotation).fullyQualifiedName,
annotation,
(converter, rawAnnotation) => convertAnnotation(converter, target, rawAnnotation)
)
);
}
objectMap[outNavProp.fullyQualifiedName] = outNavProp;
return outNavProp;
});
return vocabularyAliases;
}, {} as any);
}
/**
* @param entityTypes
* @param associations
* @param objectMap
* Converts an EntityContainer.
*
* @param converter Converter
* @param rawEntityContainer Unconverted EntityContainer
* @returns The converted EntityContainer
*/
function resolveNavigationProperties(
entityTypes: RawEntityType[],
associations: RawAssociation[],
objectMap: Record<string, any>
): void {
entityTypes.forEach((entityType) => {
entityType.navigationProperties = prepareNavigationProperties(
entityType.navigationProperties,
associations,
objectMap
);
(entityType as EntityType).resolvePath = createResolvePathFn(entityType as EntityType, objectMap);
});
function convertEntityContainer(converter: Converter, rawEntityContainer: RawEntityContainer): EntityContainer {
const convertedEntityContainer = rawEntityContainer as EntityContainer;
lazy(convertedEntityContainer, 'annotations', resolveAnnotations(converter, rawEntityContainer));
lazy(convertedEntityContainer, 'entitySets', converter.convert(converter.rawSchema.entitySets, convertEntitySet));
lazy(convertedEntityContainer, 'singletons', converter.convert(converter.rawSchema.singletons, convertSingleton));
lazy(
convertedEntityContainer,
'actionImports',
converter.convert(converter.rawSchema.actionImports, convertActionImport)
);
return convertedEntityContainer;
}
/**
* @param namespace
* @param actions
* @param objectMap
* Converts a Singleton.
*
* @param converter Converter
* @param rawSingleton Unconverted Singleton
* @returns The converted Singleton
*/
function linkActionsToEntityType(namespace: string, actions: Action[], objectMap: Record<string, any>): void {
actions.forEach((action) => {
if (!action.annotations) {
action.annotations = {};
}
if (action.isBound) {
const sourceEntityType = objectMap[action.sourceType];
action.sourceEntityType = sourceEntityType;
if (sourceEntityType) {
if (!sourceEntityType.actions) {
sourceEntityType.actions = {};
}
sourceEntityType.actions[`${namespace}.${action.name}`] = action;
}
action.returnEntityType = objectMap[action.returnType];
}
});
}
function convertSingleton(converter: Converter, rawSingleton: RawSingleton): Singleton {
const convertedSingleton = rawSingleton as Singleton;
function linkActionImportsToActions(actionImports: ActionImport[], objectMap: Record<string, any>): void {
actionImports.forEach((actionImport) => {
actionImport.action = objectMap[actionImport.actionName];
});
convertedSingleton.entityTypeName = converter.unalias(rawSingleton.entityTypeName);
lazy(convertedSingleton, 'entityType', resolveEntityType(converter, rawSingleton.entityTypeName));
lazy(convertedSingleton, 'annotations', resolveAnnotations(converter, rawSingleton as Singleton));
const _rawNavigationPropertyBindings = rawSingleton.navigationPropertyBinding;
lazy(
convertedSingleton,
'navigationPropertyBinding',
resolveNavigationPropertyBindings(
converter,
_rawNavigationPropertyBindings as Singleton['navigationPropertyBinding'],
rawSingleton
)
);
return convertedSingleton;
}
/**
* @param entitySets
* @param objectMap
* @param references
* Converts an EntitySet.
*
* @param converter Converter
* @param rawEntitySet Unconverted EntitySet
* @returns The converted EntitySet
*/
function linkEntityTypeToEntitySet(
entitySets: EntitySet[],
objectMap: Record<string, any>,
references: ReferencesWithMap
): void {
entitySets.forEach((entitySet) => {
entitySet.entityType = objectMap[entitySet.entityTypeName];
if (!entitySet.entityType) {
entitySet.entityType = objectMap[unalias(references, entitySet.entityTypeName) as string];
}
if (!entitySet.annotations) {
entitySet.annotations = {};
}
if (!entitySet.entityType.annotations) {
entitySet.entityType.annotations = {};
}
entitySet.entityType.keys.forEach((keyProp: Property) => {
keyProp.isKey = true;
});
});
function convertEntitySet(converter: Converter, rawEntitySet: RawEntitySet): EntitySet {
const convertedEntitySet = rawEntitySet as EntitySet;
convertedEntitySet.entityTypeName = converter.unalias(rawEntitySet.entityTypeName);
lazy(convertedEntitySet, 'entityType', resolveEntityType(converter, rawEntitySet.entityTypeName));
lazy(convertedEntitySet, 'annotations', resolveAnnotations(converter, rawEntitySet as EntitySet));
const _rawNavigationPropertyBindings = rawEntitySet.navigationPropertyBinding;
lazy(
convertedEntitySet,
'navigationPropertyBinding',
resolveNavigationPropertyBindings(
converter,
_rawNavigationPropertyBindings as EntitySet['navigationPropertyBinding'],
rawEntitySet
)
);
return convertedEntitySet;
}
/**
* @param singletons
* @param objectMap
* @param references
* Converts an EntityType.
*
* @param converter Converter
* @param rawEntityType Unconverted EntityType
* @returns The converted EntityType
*/
function linkEntityTypeToSingleton(
singletons: Singleton[],
objectMap: Record<string, any>,
references: ReferencesWithMap
): void {
singletons.forEach((singleton) => {
singleton.entityType = objectMap[singleton.entityTypeName];
if (!singleton.entityType) {
singleton.entityType = objectMap[unalias(references, singleton.entityTypeName) as string];
function convertEntityType(converter: Converter, rawEntityType: RawEntityType): EntityType {
const convertedEntityType = rawEntityType as EntityType;
rawEntityType.keys.forEach((keyProp: any) => {
keyProp.isKey = true;
});
lazy(convertedEntityType, 'annotations', resolveAnnotations(converter, rawEntityType));
lazy(convertedEntityType, 'keys', converter.convert(rawEntityType.keys, convertProperty));
lazy(convertedEntityType, 'entityProperties', converter.convert(rawEntityType.entityProperties, convertProperty));
lazy(
convertedEntityType,
'navigationProperties',
converter.convert(rawEntityType.navigationProperties as any[], convertNavigationProperty)
);
lazy(convertedEntityType, 'actions', () =>
converter.rawSchema.actions
.filter(
(rawAction) =>
rawAction.isBound &&
(rawAction.sourceType === rawEntityType.fullyQualifiedName ||
rawAction.sourceType === `Collection(${rawEntityType.fullyQualifiedName})`)
)
.reduce((actions, rawAction) => {
const name = `${converter.rawSchema.namespace}.${rawAction.name}`;
actions[name] = converter.getConvertedAction(rawAction.fullyQualifiedName)!;
return actions;
}, {} as EntityType['actions'])
);
convertedEntityType.resolvePath = (relativePath: string, includeVisitedObjects?: boolean) => {
const resolved = resolveTarget(converter, rawEntityType, relativePath);
if (includeVisitedObjects) {
return { target: resolved.target, visitedObjects: resolved.objectPath, messages: resolved.messages };
} else {
return resolved.target;
}
if (!singleton.annotations) {
singleton.annotations = {};
}
if (!singleton.entityType.annotations) {
singleton.entityType.annotations = {};
}
singleton.entityType.keys.forEach((keyProp: Property) => {
keyProp.isKey = true;
});
});
};
return convertedEntityType;
}
/**
* @param entityTypes
* @param objectMap
* Converts a Property.
*
* @param converter Converter
* @param rawProperty Unconverted Property
* @returns The converted Property
*/
function linkPropertiesToComplexTypes(entityTypes: EntityType[], objectMap: Record<string, any>) {
/**
* @param property
*/
function link(property: Property) {
if (!property.annotations) {
property.annotations = {};
}
function convertProperty(converter: Converter, rawProperty: RawProperty): Property {
const convertedProperty = rawProperty as Property;
try {
if (!property.type.startsWith('Edm')) {
let complexType: ComplexType | TypeDefinition;
if (property.type.startsWith('Collection')) {
const complexTypeName = property.type.substring(11, property.type.length - 1);
complexType = objectMap[complexTypeName] as ComplexType;
} else {
complexType = objectMap[property.type] as ComplexType;
}
if (complexType) {
property.targetType = complexType;
if (complexType.properties) {
complexType.properties.forEach(link);
}
}
}
} catch (sError) {
throw new Error('Property Type is not defined');
}
}
convertedProperty.type = converter.unalias(rawProperty.type);
lazy(convertedProperty, 'annotations', resolveAnnotations(converter, rawProperty));
entityTypes.forEach((entityType) => {
entityType.entityProperties.forEach(link);
lazy(convertedProperty, 'targetType', () => {
const type = rawProperty.type;
const typeName = type.startsWith('Collection') ? type.substring(11, type.length - 1) : type;
return converter.getConvertedComplexType(typeName) ?? converter.getConvertedTypeDefinition(typeName);
});
return convertedProperty;
}
/**
* @param complexTypes
* @param associations
* @param objectMap
* Converts a NavigationProperty.
*
* @param converter Converter
* @param rawNavigationProperty Unconverted NavigationProperty
* @returns The converted NavigationProperty
*/
function prepareComplexTypes(
complexTypes: RawComplexType[],
associations: RawAssociation[],
objectMap: Record<string, any>
) {
complexTypes.forEach((complexType) => {
(complexType as ComplexType).annotations = {};
complexType.properties.forEach((property) => {
if (!(property as Property).annotations) {
(property as Property).annotations = {};
}
});
function convertNavigationProperty(
converter: Converter,
rawNavigationProperty: RawV2NavigationProperty | RawV4NavigationProperty
): NavigationProperty {
const convertedNavigationProperty = rawNavigationProperty as NavigationProperty;
complexType.navigationProperties = prepareNavigationProperties(
complexType.navigationProperties,
associations,
objectMap
);
});
convertedNavigationProperty.referentialConstraint = convertedNavigationProperty.referentialConstraint ?? [];
if (isV4NavigationProperty(rawNavigationProperty)) {
convertedNavigationProperty.targetTypeName = converter.unalias(rawNavigationProperty.targetTypeName);
} else {
const associationEnd = converter.rawSchema.associations
.find((association) => association.fullyQualifiedName === rawNavigationProperty.relationship)
?.associationEnd.find((end) => end.role === rawNavigationProperty.toRole);
convertedNavigationProperty.isCollection = associationEnd?.multiplicity === '*';
convertedNavigationProperty.targetTypeName = associationEnd?.type ?? '';
}
lazy(
convertedNavigationProperty,
'targetType',
resolveEntityType(converter, (rawNavigationProperty as NavigationProperty).targetTypeName)
);
lazy(convertedNavigationProperty, 'annotations', resolveAnnotations(converter, rawNavigationProperty));
return convertedNavigationProperty;
}
/**
* Split the alias from the term value.
* Converts an ActionImport.
*
* @param references the current set of references
* @param termValue the value of the term
* @returns the term alias and the actual term value
* @param converter Converter
* @param rawActionImport Unconverted ActionImport
* @returns The converted ActionImport
*/
function splitTerm(references: ReferencesWithMap, termValue: string) {
return splitAtLast(alias(references, termValue), '.');
function convertActionImport(converter: Converter, rawActionImport: RawActionImport): ActionImport {
const convertedActionImport = rawActionImport as ActionImport;
convertedActionImport.actionName = converter.unalias(rawActionImport.actionName);
lazy(convertedActionImport, 'annotations', resolveAnnotations(converter, rawActionImport));
lazy(convertedActionImport, 'action', () => converter.getConvertedAction(rawActionImport.actionName));
return convertedActionImport;
}
/**
* Creates the function that will resolve a specific path.
* Converts an Action.
*
* @param convertedOutput
* @param objectMap
* @returns the function that will allow to resolve element globally.
* @param converter Converter
* @param rawAction Unconverted Action
* @returns The converted Action
*/
function createGlobalResolve(convertedOutput: ConvertedMetadata, objectMap: Record<string, any>) {
return function resolvePath<T>(sPath: string, resolveDirectly: boolean = false): ResolutionTarget<T> {
if (resolveDirectly) {
let targetPath = sPath;
if (!sPath.startsWith('/')) {
targetPath = `/${sPath}`;
}
const targetResolution: any = _resolveTarget(objectMap, convertedOutput, targetPath, false, true);
if (targetResolution.target) {
targetResolution.visitedObjects.push(targetResolution.target);
}
return {
target: targetResolution.target,
objectPath: targetResolution.visitedObjects
};
}
const aPathSplit = sPath.split('/');
if (aPathSplit.shift() !== '') {
throw new Error('Cannot deal with relative path');
}
const entitySetName = aPathSplit.shift();
const entitySet = convertedOutput.entitySets.find((et: EntitySet) => et.name === entitySetName);
const singleton = convertedOutput.singletons.find((et: Singleton) => et.name === entitySetName);
if (!entitySet && !singleton) {
return {
target: convertedOutput.entityContainer,
objectPath: [convertedOutput.entityContainer]
} as ResolutionTarget<T>;
}
if (aPathSplit.length === 0) {
return {
target: entitySet || singleton,
objectPath: [convertedOutput.entityContainer, entitySet || singleton]
} as ResolutionTarget<T>;
} else {
const targetResolution: any = _resolveTarget(
objectMap,
entitySet || singleton,
'/' + aPathSplit.join('/'),
false,
true
);
if (targetResolution.target) {
targetResolution.visitedObjects.push(targetResolution.target);
}
return {
target: targetResolution.target,
objectPath: targetResolution.visitedObjects
};
}
};
}
function convertAction(converter: Converter, rawAction: RawAction): Action {
const convertedAction = rawAction as Action;
type ConversionContext = {
unresolvedAnnotations: Resolveable[];
additionalAnnotations: AnnotationList[];
rawMetadata: RawMetadata;
currentSource: string;
currentTarget: any;
currentProperty?: string;
currentTerm: string;
};
convertedAction.sourceType = converter.unalias(rawAction.sourceType);
if (convertedAction.sourceType) {
lazy(convertedAction, 'sourceEntityType', resolveEntityType(converter, rawAction.sourceType));
}
function ensureAnnotations(currentTarget: any, vocAlias: string) {
if (!currentTarget.annotations) {
currentTarget.annotations = {};
convertedAction.returnType = converter.unalias(rawAction.returnType);
if (convertedAction.returnType) {
lazy(convertedAction, 'returnEntityType', resolveEntityType(converter, rawAction.returnType));
}
if (!currentTarget.annotations[vocAlias]) {
currentTarget.annotations[vocAlias] = {};
}
if (!currentTarget.annotations._annotations) {
currentTarget.annotations._annotations = {};
}
}
function processAnnotations(
currentContext: ConversionContext,
annotationList: AnnotationList,
objectMap: Record<string, any>,
bOverrideExisting: boolean
) {
const currentTarget = currentContext.currentTarget;
const currentTargetName = currentTarget.fullyQualifiedName;
annotationList.annotations.forEach((annotation: RawAnnotation) => {
currentContext.currentSource = (annotation as any).__source || (annotationList as any).__source;
const [vocAlias, vocTerm] = splitTerm(defaultReferences, annotation.term);
ensureAnnotations(currentTarget, vocAlias);
lazy(convertedAction, 'parameters', converter.convert(rawAction.parameters, convertActionParameter));
const vocTermWithQualifier = `${vocTerm}${annotation.qualifier ? '#' + annotation.qualifier : ''}`;
if (!bOverrideExisting && currentTarget.annotations?.[vocAlias]?.[vocTermWithQualifier] !== undefined) {
return;
lazy(convertedAction, 'annotations', () => {
// this.is.the.action(on.this.type) --> action: 'this.is.the.action', overload: 'on.this.type'
// this.is.the.action() --> action: 'this.is.the.action', overload: undefined
// this.is.the.action --> action: 'this.is.the.action', overload: undefined
const actionAndOverload = rawAction.fullyQualifiedName.match(/(?<action>[^()]+)(?:\((?<overload>.*)\))?/);
let rawAnnotations: RawAnnotation[] = [];
if (actionAndOverload) {
if (actionAndOverload.groups?.overload) {
rawAnnotations = converter.rawAnnotationsPerTarget[rawAction.fullyQualifiedName]?.annotations ?? [];
} else {
rawAnnotations =
converter.rawAnnotationsPerTarget[`${actionAndOverload.groups?.action}()`]?.annotations ?? [];
}
if (actionAndOverload.groups?.action && actionAndOverload.groups?.action !== rawAction.fullyQualifiedName) {
const baseAnnotations =
converter.rawAnnotationsPerTarget[actionAndOverload.groups?.action]?.annotations ?? [];
rawAnnotations = rawAnnotations.concat(baseAnnotations);
}
}
currentContext.currentTerm = annotation.term;
currentTarget.annotations[vocAlias][vocTermWithQualifier] = convertAnnotation(
annotation as Annotation,
objectMap,
currentContext
);
switch (typeof currentTarget.annotations[vocAlias][vocTermWithQualifier]) {
case 'string':
// eslint-disable-next-line no-new-wrappers
currentTarget.annotations[vocAlias][vocTermWithQualifier] = new String(
currentTarget.annotations[vocAlias][vocTermWithQualifier]
);
break;
case 'boolean':
// eslint-disable-next-line no-new-wrappers
currentTarget.annotations[vocAlias][vocTermWithQualifier] = new Boolean(
currentTarget.annotations[vocAlias][vocTermWithQualifier]
);
break;
default:
// do nothing
break;
}
if (
currentTarget.annotations[vocAlias][vocTermWithQualifier] !== null &&
typeof currentTarget.annotations[vocAlias][vocTermWithQualifier] === 'object' &&
!currentTarget.annotations[vocAlias][vocTermWithQualifier].annotations
) {
currentTarget.annotations[vocAlias][vocTermWithQualifier].annotations = {};
}
if (
currentTarget.annotations[vocAlias][vocTermWithQualifier] !== null &&
typeof currentTarget.annotations[vocAlias][vocTermWithQualifier] === 'object'
) {
currentTarget.annotations[vocAlias][vocTermWithQualifier].term = unalias(
defaultReferences,
`${vocAlias}.${vocTerm}`
);
currentTarget.annotations[vocAlias][vocTermWithQualifier].qualifier = annotation.qualifier;
currentTarget.annotations[vocAlias][vocTermWithQualifier].__source = currentContext.currentSource;
}
const annotationTarget = `${currentTargetName}@${unalias(
defaultReferences,
vocAlias + '.' + vocTermWithQualifier
)}`;
if (Array.isArray(annotation.annotations)) {
const subAnnotationList = {
target: annotationTarget,
annotations: annotation.annotations,
__source: currentContext.currentSource
};
currentContext.additionalAnnotations.push(subAnnotationList);
} else if (annotation.annotations && !currentTarget.annotations[vocAlias][vocTermWithQualifier].annotations) {
currentTarget.annotations[vocAlias][vocTermWithQualifier].annotations = annotation.annotations;
}
currentTarget.annotations._annotations[`${vocAlias}.${vocTermWithQualifier}`] =
currentTarget.annotations._annotations[unalias(defaultReferences, `${vocAlias}.${vocTermWithQualifier}`)!] =
currentTarget.annotations[vocAlias][vocTermWithQualifier];
objectMap[annotationTarget] = currentTarget.annotations[vocAlias][vocTermWithQualifier];
return createAnnotationsObject(converter, rawAction, rawAnnotations);
});
return convertedAction;
}
/**
* Process all the unresolved targets so far to try and see if they are resolveable in the end.
* Converts an ActionParameter.
*
* @param unresolvedTargets
* @param objectMap
* @param converter Converter
* @param rawActionParameter Unconverted ActionParameter
* @returns The converted ActionParameter
*/
function processUnresolvedTargets(unresolvedTargets: Resolveable[], objectMap: Record<string, any>) {
unresolvedTargets.forEach((resolvable) => {
const targetToResolve = resolvable.toResolve;
const targetStr = targetToResolve.$target;
const resolvedTarget = objectMap[targetStr];
const { annotationsTerm, annotationType } = targetToResolve;
delete targetToResolve.annotationType;
delete targetToResolve.annotationsTerm;
function convertActionParameter(
converter: Converter,
rawActionParameter: RawAction['parameters'][number]
): ActionParameter {
const convertedActionParameter = rawActionParameter as ActionParameter;
if (resolvable.inline && !(resolvedTarget instanceof String)) {
// inline the resolved target
let keys: keyof typeof targetToResolve;
for (keys in targetToResolve) {
delete targetToResolve[keys];
}
lazy(
convertedActionParameter,
'typeReference',
() =>
converter.getConvertedEntityType(rawActionParameter.type) ??
converter.getConvertedComplexType(rawActionParameter.type) ??
converter.getConvertedTypeDefinition(rawActionParameter.type)
);
Object.assign(targetToResolve, resolvedTarget);
} else {
// assign the resolved target
targetToResolve.$target = resolvedTarget;
}
lazy(convertedActionParameter, 'annotations', resolveAnnotations(converter, rawActionParameter));
if (!resolvedTarget) {
targetToResolve.targetString = targetStr;
if (annotationsTerm && annotationType) {
const oErrorMsg = {
message:
'Unable to resolve the path expression: ' +
targetStr +
'\n' +
'\n' +
'Hint: Check and correct the path values under the following structure in the metadata (annotation.xml file or CDS annotations for the application): \n\n' +
'<Annotation Term = ' +
annotationsTerm +
'>' +
'\n' +
'<Record Type = ' +
annotationType +
'>' +
'\n' +
'<AnnotationPath = ' +
targetStr +
'>'
};
addAnnotationErrorMessage(targetStr, oErrorMsg);
} else {
const property = targetToResolve.term;
const path = targetToResolve.path;
const termInfo = substringBeforeFirst(targetStr, '/');
const oErrorMsg = {
message:
'Unable to resolve the path expression: ' +
targetStr +
'\n' +
'\n' +
'Hint: Check and correct the path values under the following structure in the metadata (annotation.xml file or CDS annotations for the application): \n\n' +
'<Annotation Term = ' +
termInfo +
'>' +
'\n' +
'<PropertyValue Property = ' +
property +
' Path= ' +
path +
'>'
};
addAnnotationErrorMessage(targetStr, oErrorMsg);
}
}
});
return convertedActionParameter;
}
/**
* Merge annotation from different source together by overwriting at the term level.
* Converts a ComplexType.
*
* @param rawMetadata
* @returns the resulting merged annotations
* @param converter Converter
* @param rawComplexType Unconverted ComplexType
* @returns The converted ComplexType
*/
function mergeAnnotations(rawMetadata: RawMetadata): Record<string, AnnotationList> {
const annotationListPerTarget: Record<string, AnnotationList> = {};
Object.keys(rawMetadata.schema.annotations).forEach((annotationSource) => {
rawMetadata.schema.annotations[annotationSource].forEach((annotationList: AnnotationList) => {
const currentTargetName = unalias(rawMetadata.references, annotationList.target) as string;
(annotationList as any).__source = annotationSource;
if (!annotationListPerTarget[currentTargetName]) {
annotationListPerTarget[currentTargetName] = {
annotations: annotationList.annotations.concat(),
target: currentTargetName
};
(annotationListPerTarget[currentTargetName] as any).__source = annotationSource;
} else {
annotationList.annotations.forEach((annotation) => {
const findIndex = annotationListPerTarget[currentTargetName].annotations.findIndex(
(referenceAnnotation) => {
return (
referenceAnnotation.term === annotation.term &&
referenceAnnotation.qualifier === annotation.qualifier
);
}
);
(annotation as any).__source = annotationSource;
if (findIndex !== -1) {
annotationListPerTarget[currentTargetName].annotations.splice(findIndex, 1, annotation);
} else {
annotationListPerTarget[currentTargetName].annotations.push(annotation);
}
});
}
});
});
return annotationListPerTarget;
function convertComplexType(converter: Converter, rawComplexType: RawComplexType): ComplexType {
const convertedComplexType = rawComplexType as ComplexType;
lazy(convertedComplexType, 'properties', converter.convert(rawComplexType.properties, convertProperty));
lazy(
convertedComplexType,
'navigationProperties',
converter.convert(rawComplexType.navigationProperties as any[], convertNavigationProperty)
);
lazy(convertedComplexType, 'annotations', resolveAnnotations(converter, rawComplexType));
return convertedComplexType;
}
/**
* Converts a TypeDefinition.
*
* @param converter Converter
* @param rawTypeDefinition Unconverted TypeDefinition
* @returns The converted TypeDefinition
*/
function convertTypeDefinition(converter: Converter, rawTypeDefinition: RawTypeDefinition): TypeDefinition {
const convertedTypeDefinition = rawTypeDefinition as TypeDefinition;
lazy(convertedTypeDefinition, 'annotations', resolveAnnotations(converter, rawTypeDefinition));
return convertedTypeDefinition;
}
/**
* Convert a RawMetadata into an object representation to be used to easily navigate a metadata object and its annotation.

@@ -1425,95 +1549,45 @@ *

export function convert(rawMetadata: RawMetadata): ConvertedMetadata {
ANNOTATION_ERRORS = [];
const objectMap = buildObjectMap(rawMetadata);
resolveNavigationProperties(
rawMetadata.schema.entityTypes as EntityType[],
rawMetadata.schema.associations,
objectMap
// fall back on the default references if the caller does not specify any
if (rawMetadata.references.length === 0) {
rawMetadata.references = defaultReferences;
}
// Converter Output
const convertedOutput: ConvertedMetadata = {
version: rawMetadata.version,
namespace: rawMetadata.schema.namespace,
annotations: rawMetadata.schema.annotations,
references: defaultReferences.concat(rawMetadata.references),
diagnostics: []
} as any;
// Converter
const converter = new Converter(rawMetadata, convertedOutput);
lazy(
convertedOutput,
'entityContainer',
converter.convert(converter.rawSchema.entityContainer, convertEntityContainer)
);
(rawMetadata.schema.entityContainer as EntityContainer).annotations = {};
linkActionsToEntityType(rawMetadata.schema.namespace, rawMetadata.schema.actions as Action[], objectMap);
linkActionImportsToActions(rawMetadata.schema.actionImports, objectMap);
linkEntityTypeToEntitySet(rawMetadata.schema.entitySets as EntitySet[], objectMap, rawMetadata.references);
linkEntityTypeToSingleton(rawMetadata.schema.singletons as Singleton[], objectMap, rawMetadata.references);
linkPropertiesToComplexTypes(rawMetadata.schema.entityTypes as EntityType[], objectMap);
prepareComplexTypes(rawMetadata.schema.complexTypes as ComplexType[], rawMetadata.schema.associations, objectMap);
const unresolvedTargets: Resolveable[] = [];
const unresolvedAnnotations: AnnotationList[] = [];
const annotationListPerTarget: Record<string, AnnotationList> = mergeAnnotations(rawMetadata);
Object.keys(annotationListPerTarget).forEach((currentTargetName) => {
const annotationList = annotationListPerTarget[currentTargetName];
const objectMapElement = objectMap[currentTargetName];
if (!objectMapElement && currentTargetName?.includes('@')) {
unresolvedAnnotations.push(annotationList);
} else if (objectMapElement) {
let allTargets = [objectMapElement];
let bOverrideExisting = true;
if (objectMapElement._type === 'UnboundGenericAction') {
allTargets = objectMapElement.actions;
bOverrideExisting = false;
}
allTargets.forEach((currentTarget) => {
const currentContext: ConversionContext = {
additionalAnnotations: unresolvedAnnotations,
currentSource: (annotationList as any).__source,
currentTarget: currentTarget,
currentTerm: '',
rawMetadata: rawMetadata,
unresolvedAnnotations: unresolvedTargets
};
processAnnotations(currentContext, annotationList, objectMap, bOverrideExisting);
});
}
});
lazy(convertedOutput, 'entitySets', converter.convert(converter.rawSchema.entitySets, convertEntitySet));
lazy(convertedOutput, 'singletons', converter.convert(converter.rawSchema.singletons, convertSingleton));
lazy(convertedOutput, 'entityTypes', converter.convert(converter.rawSchema.entityTypes, convertEntityType));
lazy(convertedOutput, 'actions', converter.convert(converter.rawSchema.actions, convertAction));
lazy(convertedOutput, 'complexTypes', converter.convert(converter.rawSchema.complexTypes, convertComplexType));
lazy(convertedOutput, 'actionImports', converter.convert(converter.rawSchema.actionImports, convertActionImport));
lazy(
convertedOutput,
'typeDefinitions',
converter.convert(converter.rawSchema.typeDefinitions, convertTypeDefinition)
);
const extraUnresolvedAnnotations: AnnotationList[] = [];
unresolvedAnnotations.forEach((annotationList) => {
const currentTargetName = unalias(rawMetadata.references, annotationList.target) as string;
let [baseObj, annotationPart] = splitAtFirst(currentTargetName, '@');
const targetSplit = annotationPart.split('/');
baseObj = baseObj + '@' + targetSplit.shift();
const currentTarget = targetSplit.reduce((currentObj, path) => {
return currentObj?.[path];
}, objectMap[baseObj]);
if (!currentTarget || typeof currentTarget !== 'object') {
ANNOTATION_ERRORS.push({
message: 'The following annotation target was not found on the service ' + currentTargetName
});
} else {
const currentContext: ConversionContext = {
additionalAnnotations: extraUnresolvedAnnotations,
currentSource: (annotationList as any).__source,
currentTarget: currentTarget,
currentTerm: '',
rawMetadata: rawMetadata,
unresolvedAnnotations: unresolvedTargets
};
processAnnotations(currentContext, annotationList, objectMap, false);
convertedOutput.resolvePath = function resolvePath<T>(path: string): ResolutionTarget<T> {
const targetResolution = resolveTarget<T>(converter, undefined, path);
if (targetResolution.target) {
appendObjectPath(targetResolution.objectPath, targetResolution.target);
}
});
processUnresolvedTargets(unresolvedTargets, objectMap);
for (const property in ALL_ANNOTATION_ERRORS) {
ANNOTATION_ERRORS.push(ALL_ANNOTATION_ERRORS[property][0]);
}
(rawMetadata as any).entitySets = rawMetadata.schema.entitySets;
const extraReferences = rawMetadata.references.filter((reference: Reference) => {
return defaultReferences.find((defaultRef) => defaultRef.namespace === reference.namespace) === undefined;
});
const convertedOutput: Partial<ConvertedMetadata> = {
version: rawMetadata.version,
annotations: rawMetadata.schema.annotations,
namespace: rawMetadata.schema.namespace,
entityContainer: rawMetadata.schema.entityContainer as EntityContainer,
actions: rawMetadata.schema.actions as Action[],
actionImports: rawMetadata.schema.actionImports,
entitySets: rawMetadata.schema.entitySets as EntitySet[],
singletons: rawMetadata.schema.singletons as Singleton[],
entityTypes: rawMetadata.schema.entityTypes as EntityType[],
complexTypes: rawMetadata.schema.complexTypes as ComplexType[],
typeDefinitions: rawMetadata.schema.typeDefinitions as TypeDefinition[],
references: defaultReferences.concat(extraReferences),
diagnostics: ANNOTATION_ERRORS.concat()
return targetResolution;
};
convertedOutput.resolvePath = createGlobalResolve(convertedOutput as ConvertedMetadata, objectMap);
return convertedOutput as ConvertedMetadata;
return convertedOutput;
}

@@ -1,2 +0,2 @@

import type { ComplexType, Reference, TypeDefinition } from '@sap-ux/vocabularies-types';
import type { Index, ComplexType, Reference, TypeDefinition, ArrayWithIndex } from '@sap-ux/vocabularies-types';

@@ -424,2 +424,3 @@ export const defaultReferences: ReferencesWithMap = [

* Differentiate between a ComplexType and a TypeDefinition.
*
* @param complexTypeDefinition

@@ -449,1 +450,80 @@ * @returns true if the value is a complex type

}
/**
* Defines a lazy property.
*
* The property is initialized by calling the init function on the first read access, or by directly assigning a value.
*
* @param object The host object
* @param property The lazy property to add
* @param init The function that initializes the property's value
*/
export function lazy<Type, Key extends keyof Type>(object: Type, property: Key, init: () => Type[Key]) {
const initial = Symbol('initial');
let _value: Type[Key] | typeof initial = initial;
Object.defineProperty(object, property, {
enumerable: true,
get() {
if (_value === initial) {
_value = init();
}
return _value;
},
set(value: Type[Key]) {
_value = value;
}
});
}
/**
* Creates a function that allows to find an array element by property value.
*
* @param array The array
* @param property Elements in the array are searched by this property
* @returns A function that can be used to find an element of the array by property value.
*/
export function createIndexedFind<T>(array: Array<T>, property: keyof T) {
const index: Map<T[keyof T], T | undefined> = new Map();
return function find(value: T[keyof T]) {
const element = index.get(value);
if (element?.[property] === value) {
return element;
}
return array.find((element) => {
if (!element?.hasOwnProperty(property)) {
return false;
}
const propertyValue = element[property];
index.set(propertyValue, element);
return propertyValue === value;
});
};
}
/**
* Adds a 'get by value' function to an array.
*
* If this function is called with addIndex(myArray, 'name'), a new function 'by_name(value)' will be added that allows to
* find a member of the array by the value of its 'name' property.
*
* @param array The array
* @param property The property that will be used by the 'by_{property}()' function
* @returns The array with the added function
*/
export function addGetByValue<T, P extends Extract<keyof T, string>>(array: Array<T>, property: P) {
const indexName: keyof Index<T, P> = `by_${property}`;
if (!array.hasOwnProperty(indexName)) {
Object.defineProperty(array, indexName, { writable: false, value: createIndexedFind(array, property) });
} else {
throw new Error(`Property '${indexName}' already exists`);
}
return array as ArrayWithIndex<T, P>;
}

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc