All files / lib/model EdmxModel.js

97.36% Statements 37/38
92.85% Branches 13/14
100% Functions 14/14
97.22% Lines 35/36

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182    1x 1x 1x     5x 5x                                                                   25x 25x 25x 25x 24x       25x 1x     24x 1x     24x 1x     24x 50x                         27x   27x 1x         26x                             25x         25x       25x                                 39x                           8x 8x                   7x                                       5x 5x 5x 1x     4x       1x  
"use strict";
 
const _ = require("lodash");
const NwCsdlSchema = require("./nw/CsdlSchema");
const OasisCsdlSchema = require("./oasis/CsdlSchema");
 
function getNamespace(path) {
  let matches = /^([a-z0-9_\.]+)\..+/i.exec(path);
  return matches ? matches[1] : undefined;
}
 
/**
 * Entity Data Model for Data Services
 *
 * Implementation of packaging format for service metadata. Due to OData implementation evolution in sap,
 * there are 2 supported versions of EDMX: 4.0 and 1.0. These "version" specifies which standard is used
 * for EDMX and for CSDL (MS vs OASIS).
 *
 * Version 4.0:
 *  - is used for OData v4 (RAP) metadata and annotations
 *  - is used for OData v2 annotations (RAP and SAP Gateway Service Implementation)
 *  - implements OASIS specification 4.0
 *
 * Version 1.0:
 *  - is used for OData v2 service metadata (RAP and SAP Gateway Service Implementation)
 *  - mix of OASIS and MC specifications: it uses MC for almost everything, OASIS is used for Annotations elements
 *    and Include elements in references (MC-EDMX doesn't allow this type of reference)
 *
 * Specs here:
 * https://docs.microsoft.com/en-us/openspecs/windows_protocols/mc-edmx/5dff5e25-56a1-408b-9d44-bff6634c7d16
 * http://docs.oasis-open.org/odata/ns/edmx
 *
 * @class EdmxModel
 */
class EdmxModel {
  /**
   * Creates an instance of EdmxModel.
   * @param {Object} rawMetadata raw metadata object (JSON format from xml2js)
   * @param {Object} [settings] settings for the metadata
   * @memberof EdmxModel
   */
  constructor(rawMetadata, settings) {
    let version = rawMetadata["edmx:Edmx"].$.Version;
    let CsdlSchema = EdmxModel.getSchemaTypeByVersion(version);
    let service = EdmxModel.getService(rawMetadata);
    let schemas = _.isArray(service.Schema)
      ? service.Schema.map((s) => new CsdlSchema(s, settings, this))
      : [];
 
    // MC-EDMX allows 0, but OASIS-EDMX not
    if (schemas.length === 0) {
      throw new Error("Invalid metadata, no Schema available");
    }
 
    Object.defineProperty(this, "raw", {
      get: () => rawMetadata,
    });
 
    Object.defineProperty(this, "version", {
      get: () => version,
    });
 
    Object.defineProperty(this, "schemas", {
      get: () => schemas,
    });
  }
 
  /**
   * Get services in the model.
   *
   * @static
   * @param {Object} rawMetadata raw metadata object (JSON format from xml2js)
   * @returns {Object} edmx data service
   * @memberof EdmxModel
   */
  static getService(rawMetadata) {
    let services = rawMetadata["edmx:Edmx"]["edmx:DataServices"];
 
    if (services.length !== 1) {
      throw new Error(
        `The edmx:Edmx element specifies exactly one edmx:DataServices subelement, but it has ${services.length} of them.`
      );
    }
 
    return services[0];
  }
 
  /**
   * Get CSDL schema implementation by Edmx version.
   * '1.0': mix of MC and OASIS standards
   * '4.0': OASIS standard
   *
   * @static
   * @private
   * @param {string} version Edmx version
   * @returns {Object} CsdlSchema implementation
   * @memberof EdmxModel
   */
  static getSchemaTypeByVersion(version) {
    let CsdlSchema = {
      "1.0": NwCsdlSchema,
      "4.0": OasisCsdlSchema,
    }[version];
 
    Iif (!CsdlSchema) {
      throw new Error(`Edmx version '${version}' is not supported.`);
    }
 
    return CsdlSchema;
  }
 
  /**
   * Gets default DataService Schema from metadata object.
   *
   * @param {String} [namespace] is used to specify service namespace
   *
   * @returns {object} default Schema from metadata object.
   *
   * @memberof EdmxModel
   */
  getSchema(namespace) {
    // OASIS-EDMX: A schema is identified by a namespace. Schema namespaces MUST be unique within the scope of a document,
    // and SHOULD be globally unique. A schema cannot span more than one document.
    // MC-EDMX: A schema definition can span across more than one CSDL document. ...
    // So the more restrictive applies.
    return this.schemas.find((s) => !namespace || s.namespace === namespace);
  }
 
  /**
   * Very simple merge of edmx models. The only supported use case is that the another model just contains annotations
   * for the first model (OASIS-EDMX style back reference).
   *
   * More correct approach would be mo merge by Edmx namespace references relations. But currently there
   * is no benefit from supporting more scenarios.
   *
   * @param {Object} anotherModel another edmx model with just annotations in default schema
   * @returns {object} this to allow method chaining.
   */
  merge(anotherModel) {
    this.getSchema().applyAnnotations(anotherModel.getSchema().raw.Annotations);
    return this;
  }
 
  /**
   * Applies vendor schema extensions.
   *
   * @param {Object} [settings] parsing settings
   * @memberof EdmxModel
   */
  applySchemaExtensions(settings) {
    this.schemas.map((s) => s.applyExtensions(settings));
  }
 
  /**
   * Resolves model path expression.
   *
   * A model path is used within Annotation Path, Model Element Path, Navigation Property Path,
   * and Property Path expressions to traverse the model of a service and resolves to the model
   * element identified by the path
   *
   * Implemented only needed scope (OASIS-CSDL) and associations (MC-CSDL) (https://docs.microsoft.com/en-us/openspecs/windows_protocols/mc-csdl/77d7ccbb-bda8-444a-a160-f4581172322f).
   *
   * http://docs.oasis-open.org/odata/odata-csdl-xml/v4.01/cs01/odata-csdl-xml-v4.01-cs01.html#sec_Target
   * http://docs.oasis-open.org/odata/odata-csdl-xml/v4.01/cs01/odata-csdl-xml-v4.01-cs01.html#sec_PathExpressions
   *
   * @param {string} path model path expression
   * @returns {Object} schema element
   * @memberof EdmxModel
   */
  resolveModelPath(path) {
    let namespace = getNamespace(path);
    let schema = this.getSchema(namespace);
    if (!schema) {
      throw new Error(`Can't find schema for '${path}' model path.`);
    }
 
    return schema.resolveModelPath(path);
  }
}
 
module.exports = EdmxModel;