UNPKG

@odata2ts/odata2ts

Version:

Flexible generator to produce various TypeScript artefacts (from simple model interfaces to complete odata clients) from OData metadata files

431 lines 21.7 kB
import { __awaiter } from "tslib"; import { Modes, } from "../OptionModel.js"; import { DataModel, withNamespace } from "./DataModel.js"; import { ServiceConfigHelper } from "./ServiceConfigHelper.js"; import { NameClashValidator } from "./validation/NameClashValidator.js"; import { NoopValidator } from "./validation/NoopValidator.js"; function ifTrue(value) { return value === "true"; } function ifFalse(value) { return value === "false"; } export class Digester { constructor(version, schemas, options, namingHelper, converters) { this.version = version; this.schemas = schemas; this.options = options; this.namingHelper = namingHelper; /** * Reverse mapping from fqName to data type: EntityType, ComplexType, EnumType, or Primitive Type. */ this.model2Type = new Map(); this.mapProp = (p, entityPropConfig) => { var _a, _b; if (!p.$.Type) { throw new Error(`No type information given for property [${p.$.Name}]!`); } const configProp = this.serviceConfigHelper.findPropConfigByName(p.$.Name); const modelName = this.namingHelper.getModelPropName((entityPropConfig === null || entityPropConfig === void 0 ? void 0 : entityPropConfig.mappedName) || (configProp === null || configProp === void 0 ? void 0 : configProp.mappedName) || p.$.Name); const isCollection = !!p.$.Type.match(/^Collection\(/); let odataDataType = p.$.Type.replace(/^Collection\(([^\)]+)\)/, "$1"); // support for primitive type mapping if (this.namingHelper.includesServicePrefix(odataDataType)) { const dt = this.dataModel.getPrimitiveType(odataDataType); if (dt !== undefined) { odataDataType = dt; } } let result; // domain object known from service: // EntityType, ComplexType, EnumType if (this.namingHelper.includesServicePrefix(odataDataType)) { const modelType = this.model2Type.get(odataDataType); const [dataTypeName, dataTypePrefix] = this.namingHelper.getNameAndServicePrefix(odataDataType); const dataTypeNamespace = [dataTypePrefix]; if (!modelType) { throw new Error(`Couldn't determine model type (EntityType, ComplexType, etc) for property "${p.$.Name}"! Given data type: "${odataDataType}".`); } // special handling for enums if (modelType === "EnumType" /* DataTypes.EnumType */) { const enumConfig = this.serviceConfigHelper.findEnumTypeConfig(dataTypeNamespace, dataTypeName); result = { dataType: modelType, type: this.namingHelper.getEnumName((_a = enumConfig === null || enumConfig === void 0 ? void 0 : enumConfig.mappedName) !== null && _a !== void 0 ? _a : odataDataType), qPath: this.options.numericEnums ? "QNumericEnumPath" : "QEnumPath", qObject: isCollection ? this.options.numericEnums ? "QNumericEnumCollection" : "QEnumCollection" : undefined, qParam: this.options.numericEnums ? "QNumericEnumParam" : "QEnumParam", }; } // handling of complex & entity types else { const entityConfig = modelType === "ComplexType" /* DataTypes.ComplexType */ ? this.serviceConfigHelper.findComplexTypeConfig(dataTypeNamespace, dataTypeName) : this.serviceConfigHelper.findEntityTypeConfig(dataTypeNamespace, dataTypeName); const typeName = (_b = entityConfig === null || entityConfig === void 0 ? void 0 : entityConfig.mappedName) !== null && _b !== void 0 ? _b : odataDataType; result = { dataType: modelType, type: this.namingHelper.getModelName(typeName), qPath: "QEntityPath", qObject: this.namingHelper.getQName(typeName), qParam: "QComplexParam", }; } } // OData built-in data types else if (odataDataType.startsWith(Digester.EDM_PREFIX)) { const { outputType, qPath, qParam, qCollection } = this.mapODataType(odataDataType); const { to, toModule: typeModule, converters } = this.dataModel.getConverter(odataDataType) || {}; const type = !to ? outputType : to.startsWith(Digester.EDM_PREFIX) ? this.mapODataType(to).outputType : to; result = { dataType: "PrimitiveType" /* DataTypes.PrimitiveType */, type, typeModule, qPath, qParam, qObject: isCollection ? qCollection : undefined, converters, }; } else { throw new Error(`Unknown type [${odataDataType}]: Not 'Collection(...)', not OData type 'Edm.*', not starting with one of the namespaces!`); } return Object.assign({ odataName: p.$.Name, name: modelName, odataType: p.$.Type, fqType: odataDataType, required: ifFalse(p.$.Nullable), isCollection: isCollection, managed: typeof (entityPropConfig === null || entityPropConfig === void 0 ? void 0 : entityPropConfig.managed) !== "undefined" ? entityPropConfig.managed : configProp === null || configProp === void 0 ? void 0 : configProp.managed }, result); }; const namespaces = schemas.map((s) => [s.$.Namespace, s.$.Alias]); this.dataModel = new DataModel(namespaces, version, converters); this.serviceConfigHelper = new ServiceConfigHelper(options); this.nameValidator = options.bundledFileGeneration ? new NameClashValidator(options) : new NoopValidator(); this.collectModelTypes(schemas); } collectModelTypes(schemas) { schemas.forEach((schema) => { var _a, _b, _c; const { Namespace: ns, Alias: alias } = schema.$; (_a = schema.EnumType) === null || _a === void 0 ? void 0 : _a.forEach((et) => { this.addModel2Type(ns, alias, et.$.Name, "EnumType" /* DataTypes.EnumType */); }); (_b = schema.ComplexType) === null || _b === void 0 ? void 0 : _b.forEach((ct) => { this.addModel2Type(ns, alias, ct.$.Name, "ComplexType" /* DataTypes.ComplexType */); }); (_c = schema.EntityType) === null || _c === void 0 ? void 0 : _c.forEach((et) => { this.addModel2Type(ns, alias, et.$.Name, "ModelType" /* DataTypes.ModelType */); }); }); } addModel2Type(ns, alias, name, dt) { this.model2Type.set(withNamespace(ns, name), dt); if (alias) { this.model2Type.set(withNamespace(alias, name), dt); } } digest() { return __awaiter(this, void 0, void 0, function* () { this.digestEntityTypesAndOperations(); // delegate to version specific entity container digestion this.schemas.forEach((schema) => this.digestEntityContainer(schema)); this.dataModel.setNameValidation(this.nameValidator.validate()); return this.dataModel; }); } digestEntityTypesAndOperations() { this.schemas.forEach((schema) => { const ns = [schema.$.Namespace, schema.$.Alias]; // type definitions: alias for primitive types this.addTypeDefinition(schema.$.Namespace, schema.TypeDefinition); // enums this.addEnum(ns, schema.EnumType); // complex types this.addComplexType(ns, schema.ComplexType); // entity types this.addEntityType(ns, schema.EntityType); // V4 only: function & action types this.digestOperations(schema); }); this.postProcessModel(); this.postProcessKeys(); this.schemas.forEach((schema) => { var _a; this.analyzeModelUsage(((_a = schema.EntityContainer) === null || _a === void 0 ? void 0 : _a.length) ? schema.EntityContainer[0] : undefined); }); } getBaseModel(entityConfig, model, namespace, name, fqName) { var _a, _b; const odataName = model.$.Name; // map properties respecting the config const props = [...((_a = model.Property) !== null && _a !== void 0 ? _a : []), ...this.getNavigationProps(model)].map((p) => { var _a; const epConfig = (_a = entityConfig === null || entityConfig === void 0 ? void 0 : entityConfig.properties) === null || _a === void 0 ? void 0 : _a.find((ep) => ep.name === p.$.Name); return this.mapProp(p, epConfig); }); // support for base types, i.e. extends clause of interfaces const baseClasses = []; let finalBaseClass = undefined; if (model.$.BaseType) { baseClasses.push(model.$.BaseType); const [baseName, basePrefix] = this.namingHelper.getNameAndServicePrefix(model.$.BaseType); const baseConfig = this.serviceConfigHelper.findEntityTypeConfig([basePrefix], baseName) || this.serviceConfigHelper.findComplexTypeConfig([basePrefix], baseName); finalBaseClass = (_b = baseConfig === null || baseConfig === void 0 ? void 0 : baseConfig.mappedName) !== null && _b !== void 0 ? _b : baseName; } return { fqName, odataName, name, modelName: this.namingHelper.getModelName(name), qName: this.namingHelper.getQName(name), editableName: this.namingHelper.getEditableModelName(name), serviceName: this.namingHelper.getServiceName(name), serviceCollectionName: this.namingHelper.getCollectionServiceName(name), folderPath: this.namingHelper.getFolderPath(namespace, name), baseClasses, finalBaseClass, props, baseProps: [], abstract: ifTrue(model.$.Abstract), open: ifTrue(model.$.OpenType), genMode: Modes.qobjects, subtypes: new Set(), }; } addTypeDefinition(ns, types) { if (!types || !types.length) { return; } for (const t of types) { this.dataModel.addTypeDefinition(ns, t.$.Name, t.$.UnderlyingType); } } addEnum(namespace, models) { var _a; if (!models || !models.length) { return; } for (const et of models) { const odataName = et.$.Name; const fqName = withNamespace(namespace[0], odataName); const config = this.serviceConfigHelper.findEnumTypeConfig(namespace, odataName); const enumName = this.nameValidator.addEnumType(fqName, (config === null || config === void 0 ? void 0 : config.mappedName) || odataName); const filePath = this.namingHelper.getFolderPath(namespace[0], enumName); this.dataModel.addEnum(namespace[0], odataName, { fqName, odataName, name: enumName, modelName: this.namingHelper.getEnumName(enumName), folderPath: filePath, members: ((_a = et.Member) === null || _a === void 0 ? void 0 : _a.length) ? et.Member.map((m) => ({ name: m.$.Name, value: m.$.Value })) : [], }); } } addComplexType(namespace, models) { if (!models || !models.length) { return; } for (const model of models) { const config = this.serviceConfigHelper.findComplexTypeConfig(namespace, model.$.Name); const fqName = withNamespace(namespace[0], model.$.Name); const name = this.nameValidator.addComplexType(fqName, (config === null || config === void 0 ? void 0 : config.mappedName) || model.$.Name); const baseModel = this.getBaseModel(config, model, namespace[0], name, fqName); this.dataModel.addComplexType(namespace[0], baseModel.odataName, baseModel); } } addEntityType(namespace, models) { var _a; if (!models || !models.length) { return; } for (const model of models) { const entityConfig = this.serviceConfigHelper.findEntityTypeConfig(namespace, model.$.Name); const fqName = withNamespace(namespace[0], model.$.Name); const name = this.nameValidator.addEntityType(fqName, (entityConfig === null || entityConfig === void 0 ? void 0 : entityConfig.mappedName) || model.$.Name); const baseModel = this.getBaseModel(entityConfig, model, namespace[0], name, fqName); // key support: we add keys from this entity, // but not keys stemming from base classes (postprocess required) const keyNames = []; if ((_a = entityConfig === null || entityConfig === void 0 ? void 0 : entityConfig.keys) === null || _a === void 0 ? void 0 : _a.length) { keyNames.push(...entityConfig.keys); } else { const entity = model; if (entity.Key && entity.Key.length && entity.Key[0].PropertyRef.length) { const propNames = entity.Key[0].PropertyRef.map((key) => key.$.Name); keyNames.push(...propNames); } } this.dataModel.addEntityType(namespace[0], baseModel.odataName, Object.assign(Object.assign({}, baseModel), { id: { fqName: baseModel.fqName, modelName: this.namingHelper.getIdModelName(name), qName: this.namingHelper.getQIdFunctionName(name), }, generateId: !!keyNames.length, keyNames: keyNames, keys: [], getKeyUnion: () => keyNames.join(" | "), subtypes: new Set() })); } } /** * Check that models (ComplexType or EntityType) have been referenced in the API * as entry point or via navProp or by virtue of being a base type or subtype of those. * For these models one or two services are generated. * * In this way unnecessary service generation is prevented. For example, complex types that * are only referenced as response of an operation do not need a generated service. * * @param ec * @private */ analyzeModelUsage(ec) { var _a, _b; if ((_a = ec === null || ec === void 0 ? void 0 : ec.EntitySet) === null || _a === void 0 ? void 0 : _a.length) { ec.EntitySet.forEach((et) => this.analyze(et.$.EntityType)); } const ec4 = ec; if ((_b = ec4 === null || ec4 === void 0 ? void 0 : ec4.Singleton) === null || _b === void 0 ? void 0 : _b.length) { ec4.Singleton.forEach((singleton) => this.analyze(singleton.$.Type)); } } /** * Check usage of model types within API. * * @param fqModelName * @private */ analyze(fqModelName) { var _a; // to also resolve aliases the data model needs to be used const model = (_a = this.dataModel.getEntityType(fqModelName)) !== null && _a !== void 0 ? _a : this.dataModel.getComplexType(fqModelName); if (!(model === null || model === void 0 ? void 0 : model.fqName) || model.genMode === Modes.service) { return; } model.genMode = Modes.service; if (model) { // respect base classes if (model.baseClasses.length) { this.analyze(model.baseClasses[0]); } // include subtypes since each base class can be cast to its subtypes model.subtypes.forEach((subtype) => { this.analyze(subtype); }); model === null || model === void 0 ? void 0 : model.props.forEach((p) => { if (p.dataType === "ComplexType" /* DataTypes.ComplexType */ || p.dataType === "ModelType" /* DataTypes.ModelType */) { this.analyze(p.fqType); } }); } } postProcessModel() { // complex types const complexTypes = this.dataModel.getComplexTypes(); complexTypes.forEach((ct) => { // build up set of subtypes for each complex type this.addSubtypes(ct); // get props & keys from base types const [baseProps, _, baseAttributes] = this.collectBaseClassPropsAndKeys(ct, []); const { open } = baseAttributes; ct.baseProps = baseProps.map((bp) => (Object.assign({}, bp))); if (open) { ct.open = true; } }); // entity types const entityTypes = this.dataModel.getEntityTypes(); entityTypes.forEach((et) => { // build up set of subtypes for each entity type this.addSubtypes(et); // get props & keys from base types const [baseProps, baseKeys, baseAttributes] = this.collectBaseClassPropsAndKeys(et, []); const { fqIdName, idName, qIdName, open } = baseAttributes; et.baseProps = baseProps.map((bp) => (Object.assign({}, bp))); if (!et.keyNames.length && idName) { et.id = { fqName: fqIdName, modelName: idName, qName: qIdName, }; et.generateId = false; } if (open) { et.open = open; } et.keyNames.unshift(...baseKeys.filter((bk) => !et.keyNames.includes(bk))); }); } postProcessKeys() { const entityTypes = this.dataModel.getEntityTypes(); entityTypes.forEach((et) => { const isSingleKey = et.keyNames.length === 1; const props = [...et.baseProps, ...et.props]; et.keys = et.keyNames.map((keyName) => { const prop = props.find((p) => p.odataName === keyName); if (!prop) { throw new Error(`Key with name [${keyName}] not found in props!`); } // automatically set key prop to managed, if this is the only key of the given entity if (prop.managed === undefined) { prop.managed = !this.options.disableAutoManagedKey && isSingleKey; } return prop; }); }); } collectBaseClassPropsAndKeys(model, visitedModels) { if (visitedModels.includes(model.fqName)) { throw new Error(`Cyclic inheritance detected for model ${model.fqName}!`); } visitedModels.push(model.fqName); return model.baseClasses.reduce(([props, keys, attributes], bc) => { var _a; const baseModel = this.dataModel.getEntityType(bc) || this.dataModel.getComplexType(bc); if (!baseModel) { throw new Error(`BaseModel "${bc}" doesn't exist!`); } let { fqIdName, idName, qIdName, open } = attributes; // recursive if (baseModel.baseClasses.length) { const [parentProps, parentKeys, parentAttributes] = this.collectBaseClassPropsAndKeys(baseModel, visitedModels); props.unshift(...parentProps); keys.unshift(...parentKeys); if (parentAttributes === null || parentAttributes === void 0 ? void 0 : parentAttributes.idName) { fqIdName = parentAttributes.fqIdName; idName = parentAttributes.idName; qIdName = parentAttributes.qIdName; } if (parentAttributes === null || parentAttributes === void 0 ? void 0 : parentAttributes.open) { open = true; } } props.push(...baseModel.props); const entityModel = baseModel; if ((_a = entityModel.keyNames) === null || _a === void 0 ? void 0 : _a.length) { keys.push(...entityModel.keyNames.filter((kn) => !keys.includes(kn))); fqIdName = entityModel.id.fqName; idName = entityModel.id.modelName; qIdName = entityModel.id.qName; } if (baseModel.open) { open = true; } return [props, keys, { fqIdName, idName, qIdName, open }]; }, [[], [], { fqIdName: "", idName: "", qIdName: "", open: false }]); } addSubtypes(model, grandChildren = new Set()) { if (!model.baseClasses.length) { return; } model.baseClasses.forEach((baseClass) => { const baseType = this.dataModel.getModel(baseClass); // add subtypes & base name for q-objects baseType.subtypes.add(model.fqName); if (!baseType.qBaseName) { baseType.qBaseName = this.namingHelper.getQBaseName(baseType.name); } grandChildren.forEach((gc) => baseType.subtypes.add(gc)); // recursive grandChildren.add(model.fqName); this.addSubtypes(baseType, grandChildren); }); } } Digester.EDM_PREFIX = "Edm."; //# sourceMappingURL=DataModelDigestion.js.map