UNPKG

@sap/cds-dk

Version:

Command line client and development toolkit for the SAP Cloud Application Programming Model

1,549 lines (1,407 loc) 57.2 kB
/** * OData V2 to CSN parser */ "use strict"; let messages = require("../message").getMessages(); let common = require("../common"); let versionInfo = require('../../../package.json').version; const { warn } = require('../../util/term'); const edmxncdsdatatype = { "Edm.String": "cds.LargeString", "Edm.Boolean": "cds.Boolean", "Edm.Int16": "cds.Integer", "Edm.Int32": "cds.Integer", "Edm.Int64": "cds.Integer64", "Edm.Decimal": "cds.Decimal", "Edm.DateTime": "cds.DateTime", "Edm.DateTimeOffset": "cds.DateTime", "Edm.Time": "cds.Time", "Edm.Binary": "cds.LargeBinary", "Edm.Guid": "cds.UUID", "Edm.Double": "cds.Double", // Special handling of data types "Edm.String_m": "cds.String", // Max length "Edm.Decimal_p": "cds.Decimal", // Precision, scale "Edm.DateTime_f": "cds.Date", // sap:display-format="Date" "Edm.DateTimeOffset_f": "cds.Date", // sap:display-format="Date" "Edm.Binary_m": "cds.Binary", // Max Length "Edm.Byte_m": "cds.Integer", // Max Length "Edm.Double_p": "cds.Double", // Precision, scale // Special handling with annotations "Edm.Byte_a": "cds.Integer", "Edm.SByte_a": "cds.Integer", "Edm.Single_a": "cds.Double", "Edm.Stream_a": "cds.LargeBinary", "Edm.DateTimeOffset_a": "cds.Timestamp", //precision > 0 "Edm.DateTime_a": "cds.Timestamp" //precision > 0 }; const extendedPrimitiveTypes = [ "Edm.Duration", "Edm.Geography", "Edm.GeographyPoint", "Edm.GeographyLineString", "Edm.GeographyPolygon", "Edm.GeographyMultiPoint", "Edm.GeographyMultiLineString", "Edm.GeographyMultiPolygon", "Edm.GeographyCollection", "Edm.Geometry", "Edm.GeometryPoint", "Edm.GeometryLineString", "Edm.GeometryPolygon", "Edm.GeometryMultiPoint", "Edm.GeometryMultiLineString", "Edm.GeometryMultiPolygon", "Edm.GeometryCollection" ]; function _initialize(parserContext) { parserContext.serviceNamespace = ""; parserContext.allEntities = []; parserContext.allEntitiesMC = []; parserContext.allEntitySetMap = {}; parserContext.allEntitySetMapMC = {}; parserContext.allEntitySetNamespaces = {}; parserContext.allFunctionImports = []; parserContext.allFunctionImportsMap = {}; parserContext.allAssociations = {}; parserContext.allAssociationSets = {}; parserContext.allComplexTypes = {}; parserContext.allComplexTypeDocs = {}; parserContext.allComplexTypeNamespaces = {}; parserContext.allInheritedComplexTypes = {}; parserContext.allInheritedEntityTypes = []; parserContext.allowedNamespaces = []; parserContext.allowAllNamespaces = false; parserContext.mockServerUc = true; } // Function to extract allowed namespace attributes from given set of attributes function namespaceAttributeFilter(attributes, parserContext) { let namespaceAttributes = parserContext.allowAllNamespaces ? Object.keys(attributes).filter((attr) => attr.includes(':')) : Object.keys(attributes).filter((attr) => attr.includes(':') && parserContext.allowedNamespaces.includes(attr.split(':')[0])); namespaceAttributes = namespaceAttributes.map(attribute => { const index = attribute.indexOf(':'); let namespace = attribute.slice(0, index); let name = attribute.slice(index + 1); name = name.replace(/-/g, '.'); let formattedAttribute = {}; formattedAttribute[`${namespace}.${name}`] = _replaceSpecialCharacters(attributes[attribute]); return formattedAttribute; }); return namespaceAttributes; } //Function to generate CSN for namespace attributes function _generateNamespaceAttributes(attributes, isBefore) { let csn = ""; attributes.forEach((attr) => { let key = Object.keys(attr)[0]; if (key !== 'm.HasStream') { if (isBefore) csn = csn + ',\n'; csn = csn + `"@${key}":"${attr[key]}"`; if (!isBefore) csn = csn + ',\n'; } }); return csn; } function _isValidEDMX(jsonObj) { let isValid = false; let edmx; let dataServices; let schema; if (jsonObj) { edmx = jsonObj["edmx:Edmx"]; if (edmx) { dataServices = edmx["edmx:DataServices"]; if (dataServices) { schema = dataServices.Schema; if (schema) { isValid = true; } } } } return isValid; } function _settingMockServerUc(jsonObj, parserContext) { let EntityContainerObj = jsonObj["edmx:Edmx"]["edmx:DataServices"].Schema.EntityContainer; // Setting mockerserverUc to false when there is no EntityContainer or when the EntityContainer exists but has no elements inside it if (!EntityContainerObj || (EntityContainerObj && !EntityContainerObj.EntitySet && !EntityContainerObj.FunctionImport && !EntityContainerObj.AssociationSet)) { parserContext.mockServerUc = false; } // Importer throws an error if AssociationSet is present without corresponding entitySets else if ((EntityContainerObj && !EntityContainerObj.EntitySet && EntityContainerObj.AssociationSet)) { throw new Error(messages.MISSING_ENTITY_SETS); } } function _isValidEDMXProvided(jsonObj) { let isValid = false; if (_isValidEDMX(jsonObj)) { isValid = true; } else { throw new Error(messages.INVALID_EDMX_METADATA); } return isValid; } function _getServiceNameSpace(jsonObj) { let schemaArr = jsonObj["edmx:Edmx"]["edmx:DataServices"].Schema; let schema = schemaArr; let schemaAttributes; if (Array.isArray(schemaArr)) { // TODO: Consider multiple schema. schema = schemaArr[0]; // throw (messages.MULTIPLE_SCHEMA_FOUND); } Object.keys(schema).forEach((key) => { if (key === "_attributes") { schemaAttributes = schema[key]; } }); if (schemaAttributes) { return schemaAttributes.Namespace; } return null; } function _checkAssociation(associationSet, parserContext) { Object.keys(associationSet).forEach((i) => { if (!parserContext.allEntitiesMC.includes(associationSet[i]._attributes.EntitySet)) { throw new Error(messages.UNRESOLVED_TYPE + `'${associationSet[i]._attributes.EntitySet}'`); } }); } function _extractAssociationSet(associationSet, parserContext) { let associations = {}; Object.keys(associationSet).forEach((key) => { if (key === "_attributes") { associations.Name = associationSet[key].Name; associations.End = associationSet.End; _checkAssociation(associations.End, parserContext); parserContext.allAssociationSets[associationSet[key].Association] = associations; } }); } function _extractAllAssociationSets(associationSets, parserContext) { if (associationSets.length) { // Has many association sets in metadata for (let i = 0; i < associationSets.length; i++) { _extractAssociationSet(associationSets[i], parserContext); } } else { // Has only one association set in metadata _extractAssociationSet(associationSets, parserContext); } } function _extractAssociation(association, parserContext) { Object.keys(association).forEach((key) => { if (key === "_attributes") { parserContext.allAssociations[association[key].Name] = association; } }); } function _extractAllAssociations(associations, parserContext) { if (associations.length) { // Has many associations in metadata for (let i = 0; i < associations.length; i++) { _extractAssociation(associations[i], parserContext); } } else { // Has only one association in metadata _extractAssociation(associations, parserContext); } } function _extractEntityFromNamespace(entityName) { if (!entityName) return; let entityId = ""; let pos = entityName.lastIndexOf("."); if (pos < 0) { return entityId; } entityId = entityName.substring(pos + 1); return entityId; } function _extractEntityFromEntitySet(entitySet, parserContext) { let entityName; Object.keys(entitySet).forEach((key) => { if (key === "_attributes") { parserContext.allEntitySetMap[entitySet[key].Name] = entitySet[key].EntityType; if (parserContext.allEntitySetMapMC[entitySet[key].EntityType] === undefined) parserContext.allEntitySetMapMC[entitySet[key].EntityType] = []; parserContext.allEntitySetMapMC[entitySet[key].EntityType].push(entitySet[key].Name); entityName = _extractEntityFromNamespace(entitySet[key].EntityType); let namespaces = namespaceAttributeFilter(entitySet[key], parserContext); if (namespaces.length) parserContext.allEntitySetNamespaces[entitySet[key].Name] = namespaces; parserContext.allEntities.push(entityName); parserContext.allEntitiesMC.push(entitySet[key].Name); } }); } function _extractAllEntityFromEntitySets(entitySets, parserContext) { if (entitySets.length) { // Has many entity sets in metadata for (let i = 0; i < entitySets.length; i++) { _extractEntityFromEntitySet(entitySets[i], parserContext); } } else { // Has only one entity sets in metadata _extractEntityFromEntitySet(entitySets, parserContext); } } function _getFunctionAttributes(functionImport, serviceNamespaceParam) { let funcAttributes = []; if (functionImport["_attributes"]) { funcAttributes.push(serviceNamespaceParam + functionImport["_attributes"].Name); funcAttributes.push(functionImport["_attributes"]["m:HttpMethod"]); if (functionImport["_attributes"].ReturnType) { funcAttributes.push(functionImport["_attributes"].ReturnType); } else { funcAttributes.push(-1); } } if (functionImport["Parameter"]) { funcAttributes.push(functionImport["Parameter"]); } else { funcAttributes.push(-1); } if (functionImport["Documentation"]) { funcAttributes.push(_parseDocumentationTag(functionImport["Documentation"])); } else { funcAttributes.push(-1); } return funcAttributes; } function _extractFunctionImport(functionImport, parserContext) { //extracting the bound and unbound function import let boundFunctionImports = []; let entityName; Object.keys(functionImport).forEach((key) => { if (key === "_attributes") { if (functionImport[key]["sap:action-for"]) { //storing bound function imports entityName = _extractEntityFromNamespace(functionImport[key]["sap:action-for"]); boundFunctionImports = (parserContext.allFunctionImportsMap[entityName]) ? parserContext.allFunctionImportsMap[entityName] : []; boundFunctionImports.push(_getFunctionAttributes(functionImport, "")); parserContext.allFunctionImportsMap[entityName] = boundFunctionImports; } else { //storing unbound function imports parserContext.allFunctionImports.push( _getFunctionAttributes(functionImport, parserContext.serviceNamespace + ".") ); } } }); } function _checkFunctionImports(functionImports, parserContext) { if(functionImports.length) { functionImports.forEach((functionImport) => { if (functionImport._attributes.EntitySet && !parserContext.allEntitiesMC.includes(functionImport._attributes.EntitySet)) { throw new Error(messages.UNRESOLVED_TYPE + `'${functionImport._attributes.EntitySet}'`); } }); } else { if (functionImports._attributes.EntitySet && !parserContext.allEntitiesMC.includes(functionImports._attributes.EntitySet)) { throw new Error(messages.UNRESOLVED_TYPE + `'${functionImports._attributes.EntitySet}'`); } } } //extracting the function imports from the metadata function _extractAllFunctionImports(functionImports, parserContext) { _checkFunctionImports(functionImports, parserContext) if (functionImports.length) { //Has many function imports in metadata for (let i = 0; i < functionImports.length; i++) { _extractFunctionImport(functionImports[i], parserContext); } } else { //Has only one function import in metadata _extractFunctionImport(functionImports, parserContext); } } function _replaceSpecialCharacters(text) { return text.replace(/\\/g, "\\\\").replace(/(?:\\[rn]|[\r\n]+)+/gm, "\\n").replace(/\s+/g, ' ').replace(/"/g, "&quot;").trim(); } // to parse the documentation tag function _parseDocumentationTag(documentedContent) { let documentation = ""; if (documentedContent.Summary && documentedContent.Summary._text) { documentation = _replaceSpecialCharacters(documentedContent.Summary._text); } if (documentedContent.LongDescription && documentedContent.LongDescription._text) { if (documentedContent.Summary) documentation += "\\n\\n"; documentation += _replaceSpecialCharacters(documentedContent.LongDescription._text); } return documentation; } function _extractComplexType(complexType, parserContext) { let complexTypeKey = ""; let baseType = ""; let namespaceAttributes = {}; Object.keys(complexType).forEach((key) => { if (key === "_attributes") { complexTypeKey = parserContext.serviceNamespace + "." + complexType[key].Name; namespaceAttributes = namespaceAttributeFilter(complexType[key], parserContext); baseType = complexType[key].BaseType; if (complexType[key].Abstract || complexType[key].OpenType) { complexType.open = true; } } else if (key === "Property") { // properties = complexType.Property; } else if (key === "Documentation") { parserContext.allComplexTypeDocs[complexTypeKey] = _parseDocumentationTag(complexType.Documentation); } }); parserContext.allComplexTypes[complexTypeKey] = complexType; if (complexTypeKey && baseType) { parserContext.allInheritedComplexTypes[complexTypeKey] = baseType; } if (namespaceAttributes.length) { parserContext.allComplexTypeNamespaces[complexTypeKey] = namespaceAttributes; } } function _extractAllComplexTypes(complexTypes, parserContext) { if (complexTypes.length) { // Has many complex types in metadata for (let i = 0; i < complexTypes.length; i++) { _extractComplexType(complexTypes[i], parserContext); } } else { // Has only one association in metadata _extractComplexType(complexTypes, parserContext); } } function _getServiceEntityProperty( propertyName, dataType, length, precision, scale, displayFormat, isKey, documentation, allowedNamespaceAttributes = -1, nullable, defaultValue, collectionKind, parserContext ) { let isCollection = -1; //preprocessing of datatype in case it is a collection if (dataType && dataType.substring(0, 10) === 'Collection') { dataType = dataType.substring(11, dataType.length - 1); isCollection = 1; } //if primary key is of type Collection, an error is thrown if (isKey && isCollection === 1) { throw new Error(messages.COLLECTION_IN_KEY); } let cdsDataType = null; let hasInvalidPrecision = false; let propertyJson = ''; if (length && length > 0) { if(dataType == "Edm.Binary" && length >= 5000){ console.log(warn("MaxLength for type Edm.Binary should not exceed 5000")); } cdsDataType = edmxncdsdatatype[dataType + "_m"]; } else if (precision && precision > 0) { cdsDataType = edmxncdsdatatype[dataType + "_p"]; // Falling back to actual data type as precision has no meaning for 'Date' related data types // Using _a data type if precision is there for 'Date' related data types if (cdsDataType === undefined) { cdsDataType = edmxncdsdatatype[dataType + "_a"]; hasInvalidPrecision = true; } } else if (displayFormat && displayFormat === "Date") { cdsDataType = edmxncdsdatatype[dataType + "_f"]; } else { cdsDataType = edmxncdsdatatype[dataType]; } //checks if it's an annotation case if (!cdsDataType) { cdsDataType = edmxncdsdatatype[dataType + "_a"]; } // Lookup from complex type properties if (!cdsDataType && parserContext.allComplexTypes[dataType]) { cdsDataType = dataType; } // Lookup from inherited complex type properties if (!cdsDataType && parserContext.allInheritedComplexTypes[dataType]) { cdsDataType = dataType; } //Possibility of entity type or entity set as the data type if (!cdsDataType && parserContext.allEntitySetMapMC[dataType]) { cdsDataType = parserContext.serviceNamespace + "." + parserContext.allEntitySetMapMC[dataType][0]; } else if (!cdsDataType && parserContext.allEntitySetMap[_extractEntityFromNamespace(dataType)]) { cdsDataType = dataType; } else if (!cdsDataType && parserContext.allEntities.includes(_extractEntityFromNamespace(dataType))) { cdsDataType = dataType; } let hasCdsTypeMapping = false; if (extendedPrimitiveTypes.includes(dataType)) { cdsDataType = dataType; hasCdsTypeMapping = true; } //if the cdsDataType is undefined/ not supported if (cdsDataType === undefined) { if (propertyName) console.log(warn('"' + dataType + '" is not supported (in element:"' + propertyName + '")')); return ""; } if (propertyName) propertyJson = '"' + propertyName + '": {\n'; if (isKey) { propertyJson = propertyJson + '"key": true,\n'; } if (isCollection === 1 || collectionKind === 1) { //if annotations to be added if (edmxncdsdatatype[dataType + "_a"] && cdsDataType != "cds.Date") { if (dataType === 'Edm.Stream') propertyJson = propertyJson + '"@Core.MediaType": "application/octet-stream",\n'; else propertyJson = propertyJson + '"@odata.Type": "' + dataType + '",\n'; } //adding precision in case of DateTime datatypes if ((dataType === "Edm.DateTimeOffset" || dataType === "Edm.DateTime") && cdsDataType != "cds.Date") { if (precision && precision > 0) { propertyJson = propertyJson + '"@odata.Precision": ' + precision + ',\n'; } } if (hasCdsTypeMapping) { propertyJson = propertyJson + '"@odata.Type": "' + dataType + '"'; propertyJson = propertyJson + ',\n"items": { \n' propertyJson = propertyJson + '"type": "cds.String"'; } else { propertyJson = propertyJson + '"items": { \n' propertyJson = propertyJson + '"type":"' + cdsDataType + '"'; } if (length && length > 0) { propertyJson = propertyJson + ',\n"length":' + length; } else if (precision && precision > 0 && hasInvalidPrecision === false) { propertyJson = propertyJson + ',\n"precision":' + precision; if (scale && scale > 0) { propertyJson = propertyJson + ',\n"scale":' + scale; } else { propertyJson = propertyJson + ',\n"scale":' + 0; } } if (nullable === "false") propertyJson = propertyJson + ',\n"notNull": true'; propertyJson = propertyJson + '\n}' } else { if (hasCdsTypeMapping) { propertyJson = propertyJson + '"type": "cds.String"'; propertyJson = propertyJson + ',\n"@odata.Type": "' + dataType + '"'; } else { propertyJson = propertyJson + '"type":"' + cdsDataType + '"'; } // if annotations to be added if (edmxncdsdatatype[dataType + "_a"] && cdsDataType != "cds.Date") { if (dataType === 'Edm.Stream') propertyJson = propertyJson + ',\n"@Core.MediaType": "application/octet-stream"'; else propertyJson = propertyJson + ',\n"@odata.Type": "' + dataType + '"'; } if (length && length > 0 && !hasCdsTypeMapping) { propertyJson = propertyJson + ',\n"length":' + length; } else if (precision && precision > 0 && hasInvalidPrecision === false) { propertyJson = propertyJson + ',\n"precision":' + precision; if (scale && scale > 0) { propertyJson = propertyJson + ',\n"scale":' + scale; } else { propertyJson = propertyJson + ',\n"scale":' + 0; } } //adding precision in case of DateTime datatypes if ((dataType === "Edm.DateTimeOffset" || dataType === "Edm.DateTime") && cdsDataType !== "cds.Date" && !hasCdsTypeMapping) { if (precision && precision > 0){ propertyJson = propertyJson + ',\n"@odata.Precision": ' + precision; } } } if (documentation != -1 && documentation) { propertyJson = propertyJson + ',\n"doc":"' + documentation + '"'; } if (allowedNamespaceAttributes !== -1) { propertyJson += _generateNamespaceAttributes(allowedNamespaceAttributes, true); } if (isCollection === -1 && nullable === "false") { propertyJson = propertyJson + ',\n"notNull": true'; } if (defaultValue !== -1 && defaultValue) { propertyJson = propertyJson + ', \n"default": {\n"val": "' + defaultValue + '"\n}'; } if (propertyName) propertyJson = propertyJson + "\n}"; return propertyJson; } function _getServiceComplexType(complexTypeKey, complexType, parserContext, isOpenType) { let complexTypeCSN = '"' + complexTypeKey + '": {\n'; let complexTypeProperty; complexTypeCSN = complexTypeCSN + '"kind": "type",\n'; // checking if the ComplexType is marked open or acts as a BaseType if (isOpenType || Object.values(parserContext.allInheritedComplexTypes).indexOf(complexTypeKey) > -1) { complexTypeCSN = complexTypeCSN + '"@open": true,\n'; } complexTypeCSN = complexTypeCSN + '"@cds.external": true,\n'; if (parserContext.allComplexTypeNamespaces[complexTypeKey]) { complexTypeCSN += _generateNamespaceAttributes(parserContext.allComplexTypeNamespaces[complexTypeKey], false); } if (parserContext.allComplexTypeDocs[complexTypeKey]) complexTypeCSN = complexTypeCSN + '"doc":' + `"${parserContext.allComplexTypeDocs[complexTypeKey]}",` + "\n"; complexTypeCSN = complexTypeCSN + '"elements": {\n'; if (complexType) { if (complexType.length) { // More than one complex types for (let i = 0; i < complexType.length; i++) { complexTypeProperty = complexType[i]._attributes; if (complexType[i].Documentation) complexTypeProperty.doc = _parseDocumentationTag(complexType[i].Documentation); complexType.CollectionKind = 0; if (complexType[i]._attributes.CollectionKind === "Bag" || complexType[i]._attributes.CollectionKind === "List") { complexType.CollectionKind = 1; } let namespaceAttributes = namespaceAttributeFilter(complexTypeProperty, parserContext); const complexTypePropertyNamespace = namespaceAttributes.length ? namespaceAttributes : -1; let complexProperty = _getServiceEntityProperty( complexTypeProperty.Name, complexTypeProperty.Type, complexTypeProperty.MaxLength, complexTypeProperty.Precision, complexTypeProperty.Scale, complexTypeProperty["sap:display-format"], false, complexTypeProperty.doc, complexTypePropertyNamespace, complexTypeProperty.Nullable, complexTypeProperty.DefaultValue, complexType.CollectionKind, parserContext ); complexTypeCSN = complexTypeCSN + complexProperty; if (i !== complexType.length - 1 && complexProperty != "") { complexTypeCSN = complexTypeCSN + ",\n"; } else { complexTypeCSN = complexTypeCSN + "\n"; } } } else { // Only one complex type complexTypeProperty = complexType._attributes; if (complexType.Documentation) complexTypeProperty.doc = _parseDocumentationTag(complexType.Documentation); complexType.CollectionKind = 0; if (complexType._attributes.CollectionKind === "Bag" || complexTypeProperty.CollectionKind === "List") { complexTypeProperty.CollectionKind = 1; } let namespaceAttributes = namespaceAttributeFilter(complexTypeProperty, parserContext); const complexTypePropertyNamespace = namespaceAttributes.length ? namespaceAttributes : -1; complexTypeCSN = complexTypeCSN + _getServiceEntityProperty( complexTypeProperty.Name, complexTypeProperty.Type, complexTypeProperty.MaxLength, complexTypeProperty.Precision, complexTypeProperty.Scale, complexTypeProperty["sap:display-format"], false, complexTypeProperty.doc, complexTypePropertyNamespace, complexTypeProperty.Nullable, complexTypeProperty.DefaultValue, complexTypeProperty.CollectionKind, parserContext ); } if (complexTypeCSN && (complexTypeCSN.endsWith(",\n") || complexTypeCSN.endsWith(",\n\n"))) { complexTypeCSN = complexTypeCSN.substring(0, complexTypeCSN.lastIndexOf(",\n")) + "\n"; } } complexTypeCSN = complexTypeCSN + "}\n"; if (parserContext.allInheritedComplexTypes[complexTypeKey]) { let baseType = parserContext.allInheritedComplexTypes[complexTypeKey]; if (parserContext.allComplexTypes[baseType]) { complexTypeCSN = complexTypeCSN + ',\n"includes": ["' + baseType + '"]\n'; } else { console.log(warn("BaseType " + baseType + " couldn't be resolved")); } } complexTypeCSN = complexTypeCSN + "}\n"; return complexTypeCSN; } function _getServiceComplexTypes(parserContext) { let complexTypeCSN = ""; let complexTypesKeys = Object.keys(parserContext.allComplexTypes); let complexTypeKey; let complexType; let isOpenType; for (let i = 0; i < complexTypesKeys.length; i++) { complexTypeKey = complexTypesKeys[i]; complexType = parserContext.allComplexTypes[complexTypeKey].Property; isOpenType = parserContext.allComplexTypes[complexTypeKey].open; complexTypeCSN = complexTypeCSN + _getServiceComplexType(complexTypeKey, complexType, parserContext, isOpenType); if (i !== complexTypesKeys.length - 1) { complexTypeCSN = complexTypeCSN + ",\n"; } else { complexTypeCSN = complexTypeCSN + "\n"; } } return complexTypeCSN; } function _parseParameter(parameter, parserContext) { let csn = ""; let doc; if (parameter.Documentation) doc = _parseDocumentationTag(parameter.Documentation); let namespaceAttributes = namespaceAttributeFilter(parameter._attributes, parserContext); const parameterNamespaceAttributes = namespaceAttributes.length ? namespaceAttributes : -1; csn = csn + _getServiceEntityProperty( parameter["_attributes"].Name, parameter["_attributes"].Type, parameter["_attributes"].MaxLength, parameter["_attributes"].Precision, parameter["_attributes"].Scale, parameter["_attributes"]["sap:display-format"], false, doc, parameterNamespaceAttributes, parameter["_attributes"].Nullable, -1, parameter["_attributes"].CollectionKind, parserContext ); return csn; } function _checkParameterKey(parameter, entityKeysList) { if (entityKeysList && (Object.values(entityKeysList).indexOf(parameter._attributes.Name) > -1)) { return true; } return false; } function _parseParametersFunctionImport(parameters, entityKeysList, parserContext) { let csn = ""; let parameterCsn = ""; if (parameters.length) { for (let i = 0; i < parameters.length; i++) { // check if the parameter is a key of the entity type or not // If yes then that paramater is ignored in CSN let isKey = _checkParameterKey(parameters[i], entityKeysList); if (isKey == false) { parameterCsn = _parseParameter(parameters[i], parserContext); csn = csn + parameterCsn; if (i !== parameters.length - 1 && parameterCsn != "") { csn = csn + ",\n"; } else { csn = csn + "\n"; } } } } else { let isKey = _checkParameterKey(parameters); if (isKey === false) { csn = csn + _parseParameter(parameters, parserContext); } } if (csn && (csn.endsWith(",\n") || csn.endsWith(",\n\n"))) { csn = csn.substring(0, csn.lastIndexOf(",\n")) + "\n"; } return csn; } function _getFunctionImport(functionImport, isBound, entityKeysList, parserContext) { let noValue; let csn = ""; csn = csn + '"' + functionImport[0] + '": { \n'; csn = csn + '"kind": "'; if (functionImport[1] === "GET") { csn = csn + "function"; } else if (functionImport[1] === "POST") { csn = csn + "action"; } else { let message = functionImport[0].substring(functionImport[0].lastIndexOf(".") + 1) + " has kind " + functionImport[1] + " which is not supported."; console.log(warn(message)); return ""; } csn = csn + '"'; if (!isBound) csn = csn + ',\n"@cds.external": true'; if (functionImport[3] !== -1) { if (isBound === 0 || (isBound === 1 && functionImport[3] instanceof Array)) { let parametersCsn = _parseParametersFunctionImport(functionImport[3], entityKeysList, parserContext); if (parametersCsn != "") { csn = csn + ", \n"; csn = csn + '"params": { \n'; csn = csn + parametersCsn; csn = csn + '\n }'; } } } // if return type exist if (functionImport[2] !== -1) { let returnValue = _getServiceEntityProperty(noValue, functionImport[2], noValue, noValue, noValue, noValue, noValue, noValue, noValue, noValue, -1, noValue, parserContext); if (returnValue !== "") { csn = csn + ", \n"; csn = csn + '"returns": { \n'; //return type csn = csn + returnValue; csn = csn + '\n }'; } else { throw new Error(messages.UNRESOLVED_TYPE + `'${functionImport[2]}'`); } } // if return type is missing, add boolean only for functions if (functionImport[2] === -1 && functionImport[1] === "GET") { csn = csn + ", \n"; csn = csn + '"returns": { \n'; csn = csn + '"type": "cds.Boolean"'; csn = csn + '\n }'; } if (functionImport[4] !== -1 && functionImport[4]) { csn = csn + ',\n "doc": "' + _replaceSpecialCharacters(functionImport[4]) + '"'; } csn = csn + "\n }"; return csn; } function _getAllFunctionImports(functionImports, isBound, entityKeysList, parserContext) { let functionImportsCSN = ""; let i; let functionImportCSN = ""; for (i = 0; i < functionImports.length; i++) { functionImportCSN = _getFunctionImport(functionImports[i], isBound, entityKeysList, parserContext); functionImportsCSN = functionImportsCSN + functionImportCSN; if (i !== functionImports.length - 1 && functionImportCSN !== "") { functionImportsCSN = functionImportsCSN + ",\n"; } else { functionImportsCSN = functionImportsCSN + "\n"; } } //if last function import is omitted, remove the extra characters if (functionImportsCSN && (functionImportsCSN.endsWith(",\n") || functionImportsCSN.endsWith(",\n\n"))) { functionImportsCSN = functionImportsCSN.substring(0, functionImportsCSN.lastIndexOf(",\n")) + "\n"; } return functionImportsCSN; } function _getEntityName(entity) { return entity._attributes.Name; } function _parseEntityAttributes(attributes, parserContext) { let entityAttributes = {}; // Extract only needed entity attributes entityAttributes.Name = attributes.Name; entityAttributes.BaseType = attributes.BaseType; entityAttributes.Abstract = attributes.Abstract; entityAttributes.OpenType = attributes.OpenType; entityAttributes.allowedNamespaceAttributes = namespaceAttributeFilter(attributes, parserContext); return entityAttributes; } function _parseEntityKeys(keys) { // Care for array or non-array (only one property as key) let retKeys = []; let i; let attributes; if (keys.PropertyRef.length) { for (i = 0; i < keys.PropertyRef.length; i++) { attributes = keys.PropertyRef[i]._attributes; retKeys.push(attributes.Name); } } else { retKeys.push(keys.PropertyRef._attributes.Name); } return retKeys; } function _getPropertyAttributes(propAttributes, parserContext) { let propOthers = []; let namespaceAttributes = namespaceAttributeFilter(propAttributes, parserContext); propOthers.push(propAttributes.Type); if (propAttributes.MaxLength) { propOthers.push(propAttributes.MaxLength); } else { propOthers.push(-1); } if (propAttributes.Precision) { propOthers.push(propAttributes.Precision); } else { propOthers.push(-1); } if (propAttributes.Scale) { propOthers.push(propAttributes.Scale); } else { propOthers.push(-1); } if (propAttributes["sap:display-format"]) { propOthers.push(propAttributes["sap:display-format"]); } else { propOthers.push(-1); } if (propAttributes.doc) { propOthers.push(propAttributes.doc.replace(/"/g, '&quot;')); } else { propOthers.push(-1); } if (namespaceAttributes.length) { propOthers.push(namespaceAttributes); } else { propOthers.push(-1); } if (propAttributes.Nullable) { propOthers.push(propAttributes.Nullable); } else { propOthers.push("true"); } if (propAttributes.DefaultValue) { propOthers.push(propAttributes.DefaultValue); } else { propOthers.push(-1); } if (propAttributes.CollectionKind === 'List' || propAttributes.CollectionKind === 'Bag') { propOthers.push(1); } else { propOthers.push(-1); } return propOthers; } function _parseEntityProperty(properties, parserContext) { let propAttributes; let retProperties = {}; if (properties.length) { // Has more than one entities for (let i = 0; i < properties.length; i++) { propAttributes = properties[i]._attributes; // if the property contains documentation tag if (properties[i] && properties[i].Documentation) propAttributes.doc = _parseDocumentationTag(properties[i].Documentation); retProperties[propAttributes.Name] = _getPropertyAttributes(propAttributes, parserContext); } } else { // Has only one entity propAttributes = properties._attributes; // if the property contains documentatin tag if (properties && properties.Documentation) propAttributes.doc = _parseDocumentationTag(properties.Documentation); retProperties[propAttributes.Name] = _getPropertyAttributes(propAttributes, parserContext); } return retProperties; } function _parseNavigationProperty(navigationProperties) { let retNavProperties = {}; let navPropAttributes; if (navigationProperties.length) { // Has more than one navigation property for (let i = 0; i < navigationProperties.length; i++) { navPropAttributes = navigationProperties[i]._attributes; // if the property contains documentatin tag if (navigationProperties[i] && navigationProperties[i].Documentation) navPropAttributes.doc = _parseDocumentationTag(navigationProperties[i].Documentation); retNavProperties[navPropAttributes.Name] = navPropAttributes; } } else { // Has only one navigation property navPropAttributes = navigationProperties._attributes; // if the property contains documentatin tag if (navigationProperties && navigationProperties.Documentation) navPropAttributes.doc = _parseDocumentationTag(navigationProperties.Documentation); retNavProperties[navPropAttributes.Name] = navPropAttributes; } return retNavProperties; } function _generateCSNEntityKeys(entityKeysList, entityPropertiesMap, parserContext) { let csnEntity = ""; let propAttributes; for (let i = 0; i < entityKeysList.length; i++) { propAttributes = entityPropertiesMap[entityKeysList[i]]; if (propAttributes[7] === "true") { console.log(warn("Expected key element to be not nullable")); } let keyCsn = _getServiceEntityProperty( entityKeysList[i], propAttributes[0], propAttributes[1], propAttributes[2], propAttributes[3], propAttributes[4], true, propAttributes[5], propAttributes[6], "false", propAttributes[8], propAttributes[9], parserContext ); csnEntity = csnEntity + keyCsn; if (i !== entityKeysList.length - 1 && keyCsn != "") { csnEntity = csnEntity + ",\n"; } } if (csnEntity && (csnEntity.endsWith(",\n") || csnEntity.endsWith(",\n\n"))) { csnEntity = csnEntity.substring(0, csnEntity.lastIndexOf(",\n")) + "\n"; } return csnEntity; } function _generateCSNEntityProperties(entityKeysList, entityPropertiesMap, parserContext) { let csnEntity = ""; let entityProperties = Object.keys(entityPropertiesMap); let property; let propAttributes; if (entityKeysList.length > 0) { if (entityProperties.length > 0) { csnEntity = csnEntity + ",\n"; } else { csnEntity = csnEntity + "\n"; } } for (let i = 0; i < entityProperties.length; i++) { property = entityProperties[i]; // Include Property which are not part of keys if (entityKeysList.indexOf(property) === -1) { propAttributes = entityPropertiesMap[property]; let propertyString = _getServiceEntityProperty( property, propAttributes[0], // dataType propAttributes[1], // length propAttributes[2], // precision propAttributes[3], // scale propAttributes[4], // display false, // isKey propAttributes[5], // doc propAttributes[6], // allowed namespace propAttributes[7], // nullable propAttributes[8], // defaultVal propAttributes[9], // collection parserContext ); csnEntity = csnEntity + propertyString; if (i !== entityProperties.length - 1 && propertyString != "") { csnEntity = csnEntity + ",\n"; } else { csnEntity = csnEntity + "\n"; } } } // Last property can be an key; eliminate additional delimiter. // Last property can have undefined datatype and be ignored too if (csnEntity && (csnEntity.endsWith(",\n") || csnEntity.endsWith(",\n\n"))) { csnEntity = csnEntity.substring(0, csnEntity.lastIndexOf(",\n")) + "\n"; } return csnEntity; } function _getAssociatedEntity(associationEnds, toRole, isAssociationSetMissing, parserContext) { let entityName; let entitySetName; let entityTypeName; Object.keys(associationEnds).forEach((i) => { if (toRole === associationEnds[i]._attributes.Role) { if (isAssociationSetMissing && parserContext.mockServerUc) { entityTypeName = associationEnds[i]._attributes.Type; entitySetName = parserContext.allEntitySetMapMC[entityTypeName][0]; } else { entitySetName = associationEnds[i]._attributes.EntitySet; } if (parserContext.mockServerUc) { entityName = parserContext.serviceNamespace + "." + entitySetName; } else { entityName = parserContext.allEntitySetMap[entitySetName]; } } }); return entityName; } function _getCSNMultiplicity(associationEnds, toRole, entityName, parserContext) { let csn = ""; let multiplicity; let attributes; let stop = false; Object.keys(associationEnds).forEach((i) => { if (!stop) { attributes = associationEnds[i]._attributes; if (parserContext.mockServerUc) { let entityNameWithOutNS = parserContext.allEntitySetMap[_extractEntityFromNamespace(entityName)]; if ( toRole === attributes.Role && entityNameWithOutNS === attributes.Type ) { multiplicity = attributes.Multiplicity; stop = true; } } else { if ( toRole === attributes.Role && entityName === attributes.Type ) { multiplicity = attributes.Multiplicity; stop = true; } } } }); if (multiplicity === "1" || multiplicity === "0..1") { // When multiplicity is '1' then in CSN we NO need to generate 'cardinality' section return csn; } if (multiplicity) { csn = csn + '"cardinality": {\n'; csn = csn + '"max": "' + multiplicity + '"\n'; csn = csn + "}"; } return csn; } function _getCSNAssociatedRefrenentialConstraints( associations, toRole, entityName, navPropName, parserContext ) { let csn = ""; let multiplicity; csn = csn + '"target": "' + entityName + '"'; if (!associations) { return csn; } multiplicity = _getCSNMultiplicity( associations.End, toRole, entityName, parserContext ); if (multiplicity) { csn = csn + ",\n" + multiplicity; } return csn; } function _getCSNAssociatedEntitySet( relationshipName, toRole, navPropName, parserContext ) { let associationSet = parserContext.allAssociationSets[relationshipName]; let associationName = relationshipName.replace(parserContext.serviceNamespace + '.', ''); let association = parserContext.allAssociations[associationName]; let isAssociationSetMissing = false; // if association set is missing, use association to extract the informations if (associationSet === undefined) { isAssociationSetMissing = true; } let associationEnd = associationSet ? associationSet.End : association.End; let entityName = _getAssociatedEntity( associationEnd, toRole, isAssociationSetMissing, parserContext ); let differentAssociationNames; if (!parserContext.allAssociations[associationName]) { let array = relationshipName.split("."); if ( array != "undefined" && array != null && array.length != null && array.length > 0 ) { differentAssociationNames = parserContext.allAssociations[array[array.length - 1]]; } } else { differentAssociationNames = association; } return _getCSNAssociatedRefrenentialConstraints( differentAssociationNames, toRole, entityName, navPropName, parserContext ); } function _findAssociationType(relationshipName, fromRole, parserContext) { const associationName = relationshipName.replace(parserContext.serviceNamespace + '.', ''); const association = parserContext.allAssociations[associationName]; if (association === undefined) throw new Error(`'${associationName}' ` + messages.MISSING_ASSOCIATION); const associationEnds = association.End; let associationType; for (let i = 0; i < associationEnds.length; i++) { let potentialComposition = false; Object.keys(associationEnds[i]).forEach((key) => { if (key === "_attributes" && associationEnds[i][key].Role === fromRole) potentialComposition = true; if (key === "OnDelete" && associationEnds[i][key]._attributes.Action === "Cascade" && potentialComposition ) { associationType = "composition"; } }); } return '"type": "cds.' + (associationType === "composition" ? 'Composition"' : 'Association"') + ',\n'; } function _getServiceEntityNavigationProperty(navPropAttributes, parserContext) { let csn = ""; let navPropName; let relationshipName; let navPropDoc; let allowedNamespaceAttributes = namespaceAttributeFilter(navPropAttributes, parserContext); let fromRole; let toRole; if (!navPropAttributes) { return csn; } navPropName = navPropAttributes.Name; relationshipName = navPropAttributes.Relationship; fromRole = navPropAttributes.FromRole; toRole = navPropAttributes.ToRole; navPropDoc = navPropAttributes.doc; csn = csn + '"' + navPropName + '": {\n'; if (allowedNamespaceAttributes.length) { csn += _generateNamespaceAttributes(allowedNamespaceAttributes, false); } // if documentation exists for navigation property if (navPropDoc) csn = csn + '"doc":' + ` "${navPropDoc}",\n`; // this function call can throw error if the association is missing in the edmx file csn = csn + _findAssociationType(relationshipName, fromRole, parserContext); csn = csn + _getCSNAssociatedEntitySet( relationshipName, toRole, navPropName, parserContext ); // Convert managed associations and compositions in unmanaged with empty key to avoid // "generation" of keys, that do not exist in the external service. if (common.checkForEmptyKeys(csn, "V2")) { csn = csn + ',\n"keys": []\n'; } csn = csn + "}"; return csn; } function _generateCSNEntityNavigationProperties( entityNavigationPropertiesMap, hasProperties, parserContext ) { let csn = ""; let entityNavProperties; let navProperty; let navPropAttributes; if (!entityNavigationPropertiesMap) { return csn; } entityNavProperties = Object.keys(entityNavigationPropertiesMap); if (hasProperties && entityNavProperties.length > 0) { // Has navigation properties csn = ",\n"; } for (let i = 0; i < entityNavProperties.length; i++) { navProperty = entityNavProperties[i]; navPropAttributes = entityNavigationPropertiesMap[navProperty]; csn = csn + _getServiceEntityNavigationProperty( navPropAttributes, parserContext ); if (i < entityNavProperties.length - 1) { csn = csn + ",\n"; } } return csn; } function _getBaseTypeEntityName(entityName, parserContext) { let baseTypeName; // if one entity type has mapping to multiple entity sets, use the first entity set if (parserContext.allEntitySetMapMC[entityName]) { baseTypeName = parserContext.serviceNamespace + "." + parserContext.allEntitySetMapMC[entityName][0]; return baseTypeName; } return entityName; } function _constructServiceEntity( entityName, entityKeysList, entityPropertiesMap, entityNavigationPropertiesMap, entityAttributes, ignorePersistenceSkip, documentation, parserContext ) { let filteredNamespaces = {}; let namespaces = []; let serviceEntityName = parserContext.serviceNamespace + "." + entityName; let csnEntity = '"' + serviceEntityName + '": {\n'; let csnKeys; let csnProperties; let hasProperties; csnEntity = csnEntity + '"kind": "entity",\n'; csnEntity = csnEntity + '"@cds.external": true,\n'; if (ignorePersistenceSkip === false) { csnEntity = csnEntity + '"@cds.persistence.skip": true,\n'; } if ((entityAttributes.Abstract?.toUpperCase() === "TRUE") || (entityAttributes.OpenType?.toUpperCase() === "TRUE")) { csnEntity = csnEntity + '"@open": true,\n' } /* Merging the namespace list from entity set and entity type And in case of same label, entity set's value will have precedence */ if (parserContext.allEntitySetNamespaces[entityName]) { parserContext.allEntitySetNamespaces[entityName].forEach((item) => { filteredNamespaces[Object.keys(item)[0]] = Object.values(item)[0]; }); } if (entityAttributes.allowedNamespaceAttributes.length) { entityAttributes.allowedNamespaceAttributes.forEach((item) => { if (!filteredNamespaces[Object.keys(item)[0]]) { filteredNamespaces[Object.keys(item)[0]] = Object.values(item)[0]; } }); } Object.keys(filteredNamespaces).forEach((item) => { let obj = {}; obj[item] = filteredNamespaces[item]; namespaces.push(obj); }); if (namespaces) csnEntity += _generateNamespaceAttributes(namespaces, false); // if documentation exists for the entity if (documentation) { csnEntity = csnEntity + `"doc": "${documentation}",` + '\n'; } csnEntity = csnEntity + '"elements": {\n'; // Key Entity attributes csnKeys = _generateCSNEntityKeys(entityKeysList, entityPropertiesMap, parserContext); // Non key Entity attributes csnProperties = _generateCSNEntityProperties(entityKeysList, entityPropertiesMap, parserContext); csnProperties = csnKeys + csnProperties; //to remove additional ',\n' in case key datatype is undefined if (csnProperties && csnProperties.startsWith(",\n")) { csnProperties = csnProperties.substring(csnProperties.indexOf(",\n") + 2); } csnEntity = csnEntity + csnProperties; // No keys and No properties found hasProperties = true; if (csnKeys.trim() === "" && csnProperties.trim() === "") { hasProperties = false; } // Entity navigation properties csnEntity = csnEntity + _generateCSNEntityNavigationProperties( entityNavigationPropertiesMap, hasProperties, parserContext ); csnEntity = csnEntity + "}\n"; if (entityAttributes.BaseType) { let baseTypeName = _getBaseTypeEntityName(entityAttributes.BaseType, parserContext).replace(parserContext.serviceNamespace + '.', ''); if (parserContext.allEntitiesMC.includes(baseTypeName) || parserContext.allEntities.includes(baseTypeName)) { csnEntity = csnEntity + ',\n"includes": ["' + parserContext.serviceNamespace + '.' + baseTypeName + '"]\n'; } else { throw new Error(messages.UNRESOLVED_TYPE + `'${parserContext.serviceNamespace}.${baseTypeName}'`); } } // if the function import is bound to entitySet if (parserContext.allFunctionImportsMap[entityName]) { csnEntity = csnEntity + ', \n "actions": {\n'; csnEntity = csnEntity + _getAllFunctionImports(parserContext.allFunctionImportsMap[entityName], 1, entityKeysList, parserContext ); csnEntity = csnEntity + "}"; } // if the function import is bound to entityType else if (parserContext.allFunctionImportsMap[_extractEntityFromNamespace(parserContext.allEntitySetMap[entityName])]) { csnEntity = csnEntity + ', \n "actions": {\n'; csnEntity = csnEntity + _getAllFunctionImports( parserContext.allFunctionImportsMap[_extractEntityFromNamespace(parserContext.allEntitySetMap[entityName])], 1, entityKeysList, parserContext ); csnEntity = csnEntity + "}"; } return csnEntity; } function _addBlobElement(properties) { const blobElement = ['Edm.Stream', -1, -1, -1, -1, -1, -1]; properties['blob'] = blobElement; } function _parseServiceEntity(entityName, entity, ignorePersistenceSkip, parserContext) { let entityAttributes; let entityKeysList; let entityPropertiesMap; let entityNavigationPropertiesMap; let documentation; Object.keys(entity).forEach((key) => { if (key === "_attributes") { entityAttributes = _parseEntityAttributes(entity[key], parserContext); } else if (key.toUpperCase() === "Key".toUpperCase()) { entityKeysList = _parseEntityKeys(entity[key]); } else if (key.toUpperCase() === "Property".toUpperCase()) { entityPropertiesMap = _parseEntityProperty(entity[key], parserContext); } else if (key.toUpperCase() === "NavigationProperty".toUpperCase()) { entityNavigationPropertiesMap = _parseNavigationProperty(entity[key]); } else if (key.toUpperCase() === "Documentation".toUpperCase()) { documentation = _parseDocumentationTag(entity[key]); } }); // No keys in entity if (entityKeysList === undefined || entityKeysList == null) { entityKeysList = []; } // No properties in entity if (entityPropertiesMap === undefined || entityPropertiesMap == null) { entityPropertiesMap = []; } // No navigation properties in entity if (entityNavigationPropertiesMap === undefined || entityNavigationPropertiesMap == null) { entityNavigationPropertiesMap = []; } // If inherited entity found if (entityAttributes.BaseType) { let entityName; if (parserContext.allEntitySetMapMC && parserContext.allEntitySetMapMC[entityAttributes.BaseType]) { entityName = parserContext.allEntitySetMapMC[entityAttributes.BaseType][0]; } else entityName = _extractEntityFromNamespace(entityAttributes.BaseType); if (!parserContext.allInheritedEntityTypes.includes(entityName)) parserContext.allInheritedEntityTypes.push(entityName); } // adding blob element if entity has m:HasStream as true const checkProperty = entityAttributes["allowedNamespaceAttributes"].filter(e => e["m.HasStream"] === 'true'); if (checkProperty.length === 1) _addBlobElement(entityPropertiesMap); return _constructServiceEntity( entityName, entityKeysList, entityPropertiesMap, entityNavigationPropertiesMap, entityAttributes, ignorePersistenceSkip, documentation, parserContext ); } function _getEntitesWithNamesFromEntitySets(entityJson, parserContext) { const getEntityName = parserContext.mockServerUc ? (entity) => parserContext.allEntitySetMapMC[parserContext.serviceNamespace + "." + _getEntityName(entity)] : (entity) => [_getEntityName(entity)]; const isNameInEntitySet = parserContext.mockServerUc ? (name) => parserContext.allEntitiesMC.indexOf(name) >= 0 : (name) => parserContext.allEntities.indexOf(name) >= 0; const getEntitySetNames = entityWithName => { if (!entityWithName.name) return false; for (let i = 0; i < entityWithName.name.length; i++) { if (!isNameInEntitySet(entityWithName.name[i])) return false; } return true; }; return entityJson .map((entity) => ({ entity, name: getEntityName(entity) })) .filter((entityWithName)