UNPKG

odata-v4-server

Version:
1,036 lines (952 loc) 85.4 kB
import { Token, TokenType } from "odata-v4-parser/lib/lexer"; import * as url from "url"; import * as qs from "qs"; import * as util from "util"; import * as deepmerge from "deepmerge"; import { Transform, TransformOptions, Readable } from "stream"; import { getFunctionParameters, isIterator, isPromise, isStream } from "./utils"; import { ODataResult } from "./result"; import { ODataController, ODataControllerBase } from "./controller"; import { ResourcePathVisitor, NavigationPart, ODATA_TYPE, ODATA_TYPENAME } from "./visitor"; import * as Edm from "./edm"; import * as odata from "./odata"; import { ResourceNotFoundError, MethodNotAllowedError } from "./error"; import { ODataServer, ODataHttpContext } from "./server"; import { IODataResult } from './index'; const getODataRoot = function (context: ODataHttpContext) { return (context.protocol || "http") + "://" + (context.host || "localhost") + (context.base || ""); } const createODataContext = function (context: ODataHttpContext, entitySets, server: typeof ODataServer, resourcePath, processor) { let odataContextBase = getODataRoot(context) + "/$metadata#"; let odataContext = ""; let prevResource = null; let prevType: any = server; let selectContext = ""; if (processor.query && processor.query.$select) { selectContext = `(${processor.query.$select})`; } resourcePath.navigation.forEach((baseResource, i): string | void => { let next = resourcePath.navigation[i + 1]; let selectContextPart = (i == resourcePath.navigation.length - 1) ? selectContext : ""; if (next && next.type == TokenType.RefExpression) return; if (baseResource.type == TokenType.QualifiedEntityTypeName || baseResource.type == TokenType.QualifiedComplexTypeName) { return odataContext += `/${baseResource.name}`; } if (baseResource.type == TokenType.EntitySetName) { prevResource = baseResource; prevType = baseResource.key ? entitySets[baseResource.name].prototype.elementType : entitySets[baseResource.name]; odataContext += baseResource.name; odataContext += selectContextPart; if (baseResource.key && resourcePath.navigation.indexOf(baseResource) == resourcePath.navigation.length - 1) return odataContext += "/$entity"; if (baseResource.key) { if (baseResource.key.length > 1) { return odataContext += "(" + baseResource.key.map((key) => `${key.name}=${decodeURIComponent(key.raw)}`).join(",") + ")"; } else { return odataContext += "(" + decodeURIComponent(baseResource.key[0].raw) + ")"; } } } else if (getResourcePartFunction(baseResource.type) && !(baseResource.name in expCalls)) { odataContext = ""; if (prevResource) { let target = prevType || entitySets[prevResource.name]; if (!target) return; let propertyKey = baseResource.name.split(".").pop(); let returnType = Edm.getReturnType(target, propertyKey, server.container); let returnTypeName = Edm.getReturnTypeName(target, propertyKey, server.container); if (typeof returnType == "function") { prevType = returnType; let ctrl = server.getController(returnType); let entitySet = null; for (let prop in entitySets) { if (entitySets[prop] == ctrl) { entitySet = prop; break; } } returnType = entitySet ? entitySet + (returnTypeName.indexOf("Collection") == 0 ? selectContextPart : selectContextPart + "/$entity") : returnTypeName; } else returnType = returnTypeName; return odataContext += returnType; } else { let call = baseResource.name; let returnType = Edm.getReturnType(server, call, server.container); let returnTypeName = Edm.getReturnTypeName(server, call, server.container); if (typeof returnType == "function") { prevType = returnType; let ctrl = server.getController(returnType); let entitySet = null; for (let prop in entitySets) { if (entitySets[prop] == ctrl) { entitySet = prop; break; } } returnType = entitySet ? entitySet + (returnTypeName.indexOf("Collection") == 0 ? selectContextPart : selectContextPart + "/$entity") : returnTypeName; } else returnType = returnTypeName; return odataContext += returnType; } } if (baseResource.type == TokenType.EntityCollectionNavigationProperty) { prevResource = baseResource; odataContext += "/" + baseResource.name; prevType = baseResource.key ? Edm.getType(prevType, baseResource.name, server.container) : server.getController(<Function>Edm.getType(prevType, baseResource.name, server.container)); let ctrl = server.getController(prevType); let entitySet = null; for (let prop in entitySets) { if (entitySets[prop] == ctrl) { entitySet = prop; break; } } if (entitySet) odataContext = entitySet; odataContext += selectContextPart; if (baseResource.key && resourcePath.navigation.indexOf(baseResource) == resourcePath.navigation.length - 1) return odataContext += "/$entity"; if (baseResource.key) { if (baseResource.key.length > 1) { return odataContext += "(" + baseResource.key.map((key) => `${key.name}=${decodeURIComponent(key.raw)}`).join(",") + ")"; } else { return odataContext += "(" + decodeURIComponent(baseResource.key[0].raw) + ")"; } } return odataContext; } if (baseResource.type == TokenType.EntityNavigationProperty) { prevResource = baseResource; prevType = Edm.getType(prevType, baseResource.name, server.container); let ctrl = server.getController(prevType); let entitySet = null; for (let prop in entitySets) { if (entitySets[prop] == ctrl) { entitySet = prop; break; } } return entitySet ? odataContext = entitySet + selectContextPart + "/$entity" : odataContext += "/" + baseResource.name; } if (baseResource.type == TokenType.PrimitiveProperty || baseResource.type == TokenType.PrimitiveCollectionProperty || baseResource.type == TokenType.ComplexProperty || baseResource.type == TokenType.ComplexCollectionProperty) { prevType = Edm.getType(prevType, baseResource.name, server.container); return odataContext += "/" + baseResource.name; } }); return odataContextBase + odataContext; } const fnCaller = function (this: any, fn, params) { params = params || {}; let fnParams: any[]; fnParams = getFunctionParameters(fn); for (var i = 0; i < fnParams.length; i++) { fnParams[i] = params[fnParams[i]]; } return fn.apply(this, fnParams); }; const ODataRequestMethods: string[] = ["get", "post", "put", "patch", "delete"]; const ODataRequestResult: any = { get: ODataResult.Ok, post: ODataResult.Created, put: (result, contentType) => { return (result ? ODataResult.Created : ODataResult.NoContent)(result, contentType); }, patch: ODataResult.NoContent, delete: ODataResult.NoContent }; const expCalls = { $count: function (this: ODataResult) { return this.body && this.body.value ? (this.body.value.length || 0) : 0; }, $value: async function (this: ODataResult, processor) { try{ let prevPart = processor.resourcePath.navigation[processor.resourcePath.navigation.length - 2]; let fn = odata.findODataMethod(processor.ctrl, `${processor.method}/${prevPart.name}/$value`, prevPart.key || []); if (!fn && typeof this.elementType == "function" && Edm.isMediaEntity(this.elementType)) { fn = odata.findODataMethod(processor.ctrl, `${processor.method}/$value`, prevPart.key || []); } if (fn) { let ctrl = processor.ctrl; let params = {}; if (prevPart.key) prevPart.key.forEach((key) => params[key.name] = key.value); let fnDesc = fn; await processor.__applyParams(ctrl, fnDesc.call, params, processor.url.query, this); fn = ctrl.prototype[fnDesc.call]; if (fnDesc.key.length == 1 && prevPart.key.length == 1 && fnDesc.key[0].to != prevPart.key[0].name) { params[fnDesc.key[0].to] = params[prevPart.key[0].name]; delete params[prevPart.key[0].name]; } else { for (let i = 0; i < fnDesc.key.length; i++) { if (fnDesc.key[i].to != fnDesc.key[i].from) { params[fnDesc.key[i].to] = params[fnDesc.key[i].from]; delete params[fnDesc.key[i].from]; } } } let currentResult = fnCaller.call(ctrl, fn, params); if (isIterator(fn)) { currentResult = run(currentResult, defaultHandlers); } if (!isPromise(currentResult)) { currentResult = Promise.resolve(currentResult); } if (prevPart.type == "PrimitiveProperty" || prevPart.type == "PrimitiveKeyProperty") return currentResult.then(value => value.toString()); return currentResult; } else { if (this.stream) return Promise.resolve(this.stream); if (this.body) { let result = this.body.value || this.body; for (let prop in result) { if (prop.indexOf("@odata") >= 0) delete result[prop]; } result = (<IODataResult>result).value || result; if (typeof result == "object" && (prevPart.type == "PrimitiveProperty" || prevPart.type == "PrimitiveKeyProperty")) return Promise.resolve(result.toString()); return Promise.resolve(result); } } }catch(err){ return Promise.reject(err); } }, $ref: async function (this: any, processor) { try{ let prevPart = processor.resourcePath.navigation[processor.resourcePath.navigation.length - 2]; let routePart = processor.resourcePath.navigation[processor.resourcePath.navigation.length - 3]; let fn = odata.findODataMethod(processor.prevCtrl, processor.method + "/" + prevPart.name + "/$ref", routePart.key || []); if (processor.method == "get") { return { "@odata.context": `${getODataRoot(processor.context)}/$metadata#$ref`, "@odata.id": `${this.body["@odata.id"]}/${prevPart.name}` }; } if (!fn) throw new ResourceNotFoundError(); let linkUrl = (processor.resourcePath.id || (processor.body || {})["@odata.id"] || "").replace(getODataRoot(processor.context), ""); let linkAst, linkPath, linkPart; if (linkUrl) { linkUrl = decodeURIComponent(linkUrl); processor.emit("header", { "OData-EntityId": linkUrl }); linkAst = processor.serverType.parser.odataUri(linkUrl, { metadata: processor.serverType.$metadata().edmx }); linkPath = await new ResourcePathVisitor(processor.serverType, processor.entitySets).Visit(linkAst); linkPart = linkPath.navigation[linkPath.navigation.length - 1]; } else linkPart = prevPart; let ctrl = processor.prevCtrl; let params = {}; if (routePart.key) routePart.key.forEach((key) => params[key.name] = key.value); let fnDesc = fn; await processor.__applyParams(ctrl, fnDesc.call, params, processor.url.query, this); fn = ctrl.prototype[fnDesc.call]; if (fnDesc.key.length == 1 && routePart.key.length == 1 && fnDesc.key[0].to != routePart.key[0].name) { params[fnDesc.key[0].to] = params[routePart.key[0].name]; delete params[routePart.key[0].name]; } else { for (let i = 0; i < fnDesc.key.length; i++) { if (fnDesc.key[i].to != fnDesc.key[i].from) { params[fnDesc.key[i].to] = params[fnDesc.key[i].from]; delete params[fnDesc.key[i].from]; } } } let linkParams = {}; if (linkPart.key) linkPart.key.forEach((key) => linkParams[key.name] = key.value); if (fnDesc.link.length == 1 && linkPart.key.length == 1 && fnDesc.link[0].to != linkPart.key[0].name) { params[fnDesc.link[0].to] = linkParams[linkPart.key[0].name]; } else { for (let i = 0; i < fnDesc.link.length; i++) { params[fnDesc.link[i].to] = linkParams[fnDesc.link[i].from]; } } let currentResult = fnCaller.call(ctrl, fn, params); if (isIterator(fn)) { currentResult = run(currentResult, defaultHandlers); } if (!isPromise(currentResult)) { currentResult = Promise.resolve(currentResult); } return currentResult; }catch(err){ return Promise.reject(err); } } }; const getResourcePartFunction = (type) => { switch (type) { case "PrimitiveFunctionImportCall": case "PrimitiveCollectionFunctionImportCall": case "ComplexFunctionImportCall": case "ComplexCollectionFunctionImportCall": case "EntityFunctionImportCall": case "EntityCollectionFunctionImportCall": case "ActionImportCall": case "ActionImport": return "__actionOrFunctionImport"; case "BoundPrimitiveFunctionCall": case "BoundPrimitiveCollectionFunctionCall": case "BoundComplexFunctionCall": case "BoundComplexCollectionFunctionCall": case "BoundEntityFunctionCall": case "BoundEntityCollectionFunctionCall": case "BoundActionCall": case "BoundAction": case "CountExpression": case "ValueExpression": case "RefExpression": return "__actionOrFunction"; default: return null; } }; const jsPrimitiveTypes = [ Object, String, Boolean, Number, Date ]; const writeMethods = [ "delete", "post", "put", "patch" ]; export type GeneratorAction = (value?) => any; export type PromiseGeneratorHandler = Promise<any> | void; export namespace ODataGeneratorHandlers { export function PromiseHandler(request: any, next: GeneratorAction): PromiseGeneratorHandler { if (isPromise(request)) { return request.then(next); } } export function StreamHandler(request: any, next: GeneratorAction): PromiseGeneratorHandler { if (isStream(request)) { return new Promise((resolve, reject) => { request.on("end", resolve); request.on("error", reject); }).then(next); } } export function GeneratorHandler(request: any, next: GeneratorAction): PromiseGeneratorHandler { if (isIterator(request)) { return run(request(), defaultHandlers).then(next); } } } const defaultHandlers = [ ODataGeneratorHandlers.GeneratorHandler, ODataGeneratorHandlers.PromiseHandler, ODataGeneratorHandlers.StreamHandler ]; function run(iterator, handlers) { function id(x) { return x; } function iterate(value?) { let next = iterator.next(value); let request = next.value; let nextAction = next.done ? id : iterate; for (let handler of handlers) { let action = handler(request, nextAction); if (typeof action != "undefined") return action; } return nextAction(request); } return iterate(); } class ODataStreamWrapper extends Transform { buffer: any[]; constructor() { super(<TransformOptions>{ objectMode: true }); this.buffer = []; } _transform(chunk: any, _: string, done: Function) { this.buffer.push(chunk); if (typeof done == "function") done(); } protected _flush(done?: Function) { if (typeof done == "function") done(); } toPromise():Promise<any[]> { return new Promise((resolve, reject) => { this.on("finish", () => { resolve(this.buffer); }); this.on("error", reject); }); } } class StreamWrapper { stream: any constructor(value) { this.stream = value; } } export enum ODataMetadataType { minimal, full, none } export interface ODataProcessorOptions { disableEntityConversion: boolean metadata: ODataMetadataType objectMode: boolean } export class ODataProcessor extends Transform { private serverType: typeof ODataServer private options: ODataProcessorOptions private ctrl: typeof ODataController private prevCtrl: typeof ODataController private instance: ODataController private resourcePath: ResourcePathVisitor private workflow: any[] private context: ODataHttpContext private method: string private url: any private query: any private entitySets: { [entitySet: string]: typeof ODataController } private odataContext: string private body: any private streamStart = false; private streamEnabled = false; private streamObject = false; private streamEnd = false; private streamInlineCount: number; private elementType: any; private resultCount = 0; constructor(context, server, options?: ODataProcessorOptions) { super(<TransformOptions>{ objectMode: true }); this.context = context; this.serverType = server; this.options = options || <ODataProcessorOptions>{}; let method = this.method = context.method.toLowerCase(); if (ODataRequestMethods.indexOf(method) < 0) throw new MethodNotAllowedError(); context.url = decodeURIComponent(context.url); this.url = url.parse(context.url); this.query = qs.parse(this.url.query); let ast = this.serverType.parser.odataUri(context.url, { metadata: this.serverType.$metadata().edmx }); if (this.serverType.validator) { this.serverType.validator(ast); } let entitySets = this.entitySets = odata.getPublicControllers(this.serverType); this.workflow = [async (body) => { let resourcePath = this.resourcePath = await new ResourcePathVisitor(this.serverType, this.entitySets).Visit(ast); this.odataContext = createODataContext(context, entitySets, server, resourcePath, this); if (resourcePath.navigation.length == 0) throw new ResourceNotFoundError(); this.workflow.push(...resourcePath.navigation.map((part, i) => { let next = resourcePath.navigation[i + 1]; if (next && next.type == TokenType.RefExpression) return null; let fn = getResourcePartFunction(part.type) || ("__" + part.type); switch (fn) { case "__actionOrFunction": return this.__actionOrFunction.call(this, part); case "__actionOrFunctionImport": return this.__actionOrFunctionImport.call(this, part); case "__QualifiedEntityTypeName": case "__QualifiedComplexTypeName": return this.__qualifiedTypeName.call(this, part); case "__PrimitiveKeyProperty": case "__PrimitiveCollectionProperty": case "__ComplexProperty": case "__ComplexCollectionProperty": case "__PrimitiveProperty": return this.__PrimitiveProperty.call(this, part); case "__EntitySetName": return this.__EntitySetName.call(this, part); case "__EntityCollectionNavigationProperty": return this.__EntityCollectionNavigationProperty.call(this, part); case "__EntityNavigationProperty": return this.__EntityNavigationProperty.call(this, part); default: return null; } }).filter(it => !!it)); this.workflow.push((result) => { if (result && result.statusCode && result.statusCode == 201) { this.emit("header", { "Location": result.body["@odata.id"] }); } return Promise.resolve(result); }); return body; }]; } _transform(chunk: any, _: string, done: Function) { if (this.streamEnabled) { if (!(chunk instanceof Buffer)) { this.streamObject = true; if (!this.streamStart) { if (!this.options.objectMode) { this.push("{"); if (this.options.metadata != ODataMetadataType.none) { this.push(`"@odata.context":"${this.odataContext}",`); } this.push('"value":['); } } else if (!this.options.objectMode && this.resultCount > 0) this.push(','); try { this.streamStart = true; if (chunk instanceof Object) { if (chunk["@odata.count"] || chunk.inlinecount) { this.streamInlineCount = chunk["@odata.count"] || chunk.inlinecount; if (Object.keys(chunk).length == 1) { return typeof done == "function" ? done() : null; } else { delete chunk["@odata.count"]; delete chunk.inlinecount; } } let entity = {}; let defer; if (this.ctrl) defer = this.__appendLinks(this.ctrl, this.elementType || this.ctrl.prototype.elementType, entity, chunk); let deferConvert = this.__convertEntity(entity, chunk, this.elementType || this.ctrl.prototype.elementType, this.resourcePath.includes, this.resourcePath.select); defer = defer ? defer.then(_ => deferConvert) : deferConvert; defer.then(() => { chunk = this.options.objectMode ? entity : JSON.stringify(entity); this.push(chunk); this.resultCount++; if (typeof done == "function") done(); }, (err) => { console.log(err); if (typeof done == "function") done(err); }); } else { this.push(JSON.stringify(chunk)); this.resultCount++; if (typeof done == "function") done(); } } catch (err) { console.log(err); if (typeof done == "function") done(err); } } else { this.streamStart = true; this.push(chunk); this.resultCount++; if (typeof done == "function") done(); } } else { this.resultCount++; if (typeof done == "function") done(); } } protected _flush(done?: Function) { if (this.streamEnabled && this.streamObject) { if (this.options.objectMode) { let flushObject: any = { value: [], elementType: this.elementType || this.ctrl.prototype.elementType }; if (this.options.metadata != ODataMetadataType.none) { flushObject["@odata.context"] = this.odataContext; } if (this.streamStart && typeof this.streamInlineCount == "number") { flushObject["@odata.count"] = this.streamInlineCount; } this.push(flushObject); } else { if (this.streamStart) { if (typeof this.streamInlineCount == "number") { this.push(`],"@odata.count":${this.streamInlineCount}}`); } else this.push("]}"); } else { if (this.options.metadata == ODataMetadataType.none) { this.push('{"value":[]}'); } else { this.push(`{"@odata.context":"${this.odataContext}","value":[]}`); } } } } else if (this.streamEnabled && !this.streamStart) { if (this.options.metadata == ODataMetadataType.none) { this.push('{"value":[]}'); } else { this.push(`{"@odata.context":"${this.odataContext}","value":[]}`); } } this.streamEnd = true; if (typeof done == "function") done(); } private __qualifiedTypeName(part: NavigationPart): Function { return (result) => { result.elementType = part.node[ODATA_TYPE]; return result; }; } private __EntityCollectionNavigationProperty(part: NavigationPart): Function { return async (result) => { try{ let resultType = result.elementType; let elementType = <Function>Edm.getType(resultType, part.name, this.serverType.container); let partIndex = this.resourcePath.navigation.indexOf(part); let method = writeMethods.indexOf(this.method) >= 0 && partIndex < this.resourcePath.navigation.length - 1 ? "get" : this.method; let fn: any = odata.findODataMethod(this.ctrl, `${method}/${part.name}`, part.key); if (fn) { let ctrl = this.ctrl; let fnDesc = fn; let params = {}; if (part.key) part.key.forEach((key) => params[key.name] = key.value); await this.__applyParams(ctrl, fnDesc.call, params, this.url.query, result); fn = ctrl.prototype[fnDesc.call]; if (fnDesc.key.length == 1 && part.key.length == 1 && fnDesc.key[0].to != part.key[0].name) { params[fnDesc.key[0].to] = params[part.key[0].name]; delete params[part.key[0].name]; } else { for (let i = 0; i < fnDesc.key.length; i++) { if (fnDesc.key[i].to != fnDesc.key[i].from) { params[fnDesc.key[i].to] = params[fnDesc.key[i].from]; delete params[fnDesc.key[i].from]; } } } if (part.key) part.key.forEach((key) => params[key.name] = key.value); this.elementType = elementType; return this.__read(ctrl, part, params, result, fn, elementType).then((result) => { this.ctrl = this.serverType.getController(elementType); return result; }); } else { let ctrl = this.serverType.getController(elementType); let foreignKeys = Edm.getForeignKeys(resultType, part.name); let typeKeys = Edm.getKeyProperties(resultType); result.foreignKeys = {}; let foreignFilter = (await Promise.all(foreignKeys.map(async key => { result.foreignKeys[key] = result.body[typeKeys[0]]; return `${key} eq ${await Edm.escape(result.body[typeKeys[0]], Edm.getTypeName(elementType, key, this.serverType.container))}`; }))).join(" and "); let params = {}; if (part.key) part.key.forEach((key) => params[key.name] = key.value); return this.__read(ctrl, part, params, result, foreignFilter); } }catch(err){ return Promise.reject(err); } }; } private __EntityNavigationProperty(part: NavigationPart): Function { return async (result) => { try{ let resultType = result.elementType; let elementType = <Function>Edm.getType(resultType, part.name, this.serverType.container); let partIndex = this.resourcePath.navigation.indexOf(part); let method = writeMethods.indexOf(this.method) >= 0 && partIndex < this.resourcePath.navigation.length - 1 ? "get" : this.method; let fn: any = odata.findODataMethod(this.ctrl, `${method}/${part.name}`, part.key); if (fn) { let ctrl = this.ctrl; let fnDesc = fn; let params = {}; if (part.key) part.key.forEach((key) => params[key.name] = key.value); await this.__applyParams(ctrl, fnDesc.call, params, this.url.query, result); fn = ctrl.prototype[fnDesc.call]; if (fnDesc.key.length == 1 && part.key.length == 1 && fnDesc.key[0].to != part.key[0].name) { params[fnDesc.key[0].to] = params[part.key[0].name]; delete params[part.key[0].name]; } else { for (let i = 0; i < fnDesc.key.length; i++) { if (fnDesc.key[i].to != fnDesc.key[i].from) { params[fnDesc.key[i].to] = params[fnDesc.key[i].from]; delete params[fnDesc.key[i].from]; } } } this.elementType = elementType; return this.__read(ctrl, part, params, result, fn, elementType).then((result) => { this.ctrl = this.serverType.getController(elementType); return result; }); } else { let ctrl = this.serverType.getController(elementType); let foreignKeys = Edm.getForeignKeys(resultType, part.name); result.foreignKeys = {}; (<any>part).key = foreignKeys.map((key) => { result.foreignKeys[key] = result.body[key]; return { name: key, value: result.body[key] }; }); let params = {}; if (part.key) part.key.forEach((key) => params[key.name] = key.value); return this.__read(ctrl, part, params, result); } }catch(err){ return Promise.reject(err); } }; } private __PrimitiveProperty(part: NavigationPart): Function { return async (result) => { try{ return new Promise(async (resolve, reject) => { this.__enableStreaming(part); let currentResult; let prevPart = this.resourcePath.navigation[this.resourcePath.navigation.indexOf(part) - 1]; let fn = odata.findODataMethod(this.ctrl, `${this.method}/${part.name}`, prevPart.key || []) || odata.findODataMethod(this.ctrl, `${this.method}/${part.name}/$value`, prevPart.key || []); if (!fn && this.method != "get") { fn = this.method == "delete" ? odata.findODataMethod(this.ctrl, "patch", prevPart.key || []) : odata.findODataMethod(this.ctrl, `${this.method}`, prevPart.key || []); if (fn) { let body = this.body; if (Edm.getTypeName(result.elementType, part.name, this.serverType.container) != "Edm.Stream") body = body.body || body; this.body = {}; this.body[part.name] = this.method == "delete" ? null : body.value || body; } } if (fn) { let ctrl = this.prevCtrl; let params = {}; if (prevPart.key) prevPart.key.forEach((key) => params[key.name] = key.value); let fnDesc = fn; await this.__applyParams(ctrl, fnDesc.call, params, this.url.query, result); fn = ctrl.prototype[fnDesc.call]; if (fnDesc.key.length == 1 && prevPart.key.length == 1 && fnDesc.key[0].to != prevPart.key[0].name) { params[fnDesc.key[0].to] = params[prevPart.key[0].name]; delete params[prevPart.key[0].name]; } else { for (let i = 0; i < fnDesc.key.length; i++) { if (fnDesc.key[i].to != fnDesc.key[i].from) { params[fnDesc.key[i].to] = params[fnDesc.key[i].from]; delete params[fnDesc.key[i].from]; } } } this.elementType = Edm.getType(result.elementType, part.name, this.serverType.container) || Object; if (typeof this.elementType == "string") this.elementType = Object; currentResult = fnCaller.call(ctrl, fn, params); if (isIterator(fn)) { currentResult = run(currentResult, defaultHandlers); } if (!isPromise(currentResult)) { currentResult = Promise.resolve(currentResult); } } else { let value = result.body[part.name]; if (value instanceof StreamWrapper) { value = value.stream; } currentResult = Promise.resolve(value); } if (this.method == "get") { currentResult.then((value) => { try { result.body = { "@odata.context": this.options.metadata != ODataMetadataType.none ? result.body["@odata.context"] : undefined, value: value }; let elementType = result.elementType; //if (value instanceof Object) result.elementType = Edm.isEnumType(result.elementType, part.name) ? Edm.getTypeName(result.elementType, part.name, this.serverType.container) : Edm.getType(result.elementType, part.name, this.serverType.container) || Object; if (value && (isStream(value) || isStream(value.stream))) { this.emit("header", { "Content-Type": Edm.getContentType(elementType.prototype, part.name) || value.contentType || "application/octet-stream" }); if (value.stream) value = value.stream; value.pipe(this); value.on("end", resolve); value.on("error", reject); } else { if (this.streamEnabled && this.streamStart) delete result.body; if (result.stream) delete result.stream; resolve(result); } } catch (err) { console.log(err); reject(err); } }, reject); } else { ODataResult.NoContent(currentResult).then(resolve, reject); } }); }catch(err){ return Promise.reject(err); } }; } private __read(ctrl: typeof ODataController, part: any, params: any, data?: any, filter?: string | Function, elementType?: any, include?, select?) { return new Promise(async (resolve, reject) => { try{ select = select || this.resourcePath.select; if (this.ctrl) this.prevCtrl = this.ctrl; else this.prevCtrl = ctrl; this.ctrl = ctrl; let method = writeMethods.indexOf(this.method) >= 0 && this.resourcePath.navigation.indexOf(part) < this.resourcePath.navigation.length - 1 ? "get" : this.method; this.instance = new ctrl(); let fn; if (typeof filter == "string" || !filter) { fn = odata.findODataMethod(ctrl, method, part.key); if (!fn) return reject(new ResourceNotFoundError()); let queryString = filter ? `$filter=${filter}` : (include || this.url).query; if (include && filter && include.query && !include.query.$filter) { include.query.$filter = filter; queryString = Object.keys(include.query).map(p => { return `${p}=${include.query[p]}`; }).join("&"); } else if ((include && filter && include.query) || (!include && this.resourcePath.navigation.indexOf(part) == this.resourcePath.navigation.length - 1)) { queryString = Object.keys((include || this).query).map(p => { if (p == "$filter" && filter) { (include || this).query[p] = `(${(include || this).query[p]}) and (${filter})`; } return `${p}=${(include || this).query[p]}`; }).join("&") || queryString; } if (queryString && typeof queryString == "object") { queryString = Object.keys(queryString).map(p => { return `${p}=${queryString[p]}`; }).join("&"); } if (typeof fn != "function") { let fnDesc = fn; fn = ctrl.prototype[fnDesc.call]; if (fnDesc.key.length == 1 && part.key.length == 1 && fnDesc.key[0].to != part.key[0].name) { params[fnDesc.key[0].to] = params[part.key[0].name]; delete params[part.key[0].name]; } else { for (let i = 0; i < fnDesc.key.length; i++) { if (fnDesc.key[i].to != fnDesc.key[i].from) { params[fnDesc.key[i].to] = params[fnDesc.key[i].from]; delete params[fnDesc.key[i].from]; } } } await this.__applyParams(ctrl, fnDesc.call, params, queryString, undefined, include); } else await this.__applyParams(ctrl, method, params, queryString, undefined, include); } else fn = filter; if (!include) this.__enableStreaming(part); let currentResult: any; switch (method) { case "get": case "delete": currentResult = fnCaller.call(ctrl, fn, params); break; case "post": this.odataContext += "/$entity"; case "put": case "patch": let body = data ? Object.assign(this.body || {}, data.foreignKeys) : this.body; let bodyParam = odata.getBodyParameter(ctrl, fn.name); let typeParam = odata.getTypeParameter(ctrl, fn.name); if (typeParam) { params[typeParam] = (body["@odata.type"] || (`${(<any>ctrl.prototype.elementType).namespace}.${(<any>ctrl.prototype.elementType).name}`)).replace(/^#/, ""); } if (bodyParam) { await this.__deserialize(body, ctrl.prototype.elementType); this.__stripOData(body); params[bodyParam] = body; } if (!part.key) { let properties: string[] = Edm.getProperties((elementType || ctrl.prototype.elementType).prototype); properties.forEach((prop) => { if (Edm.isKey(elementType || ctrl.prototype.elementType, prop)) { params[prop] = (this.body || {})[prop] || ((data || {}).body || {})[prop]; } }); } currentResult = fnCaller.call(ctrl, fn, params); break; } if (isIterator(fn)) { currentResult = run(currentResult, defaultHandlers); } if (!isPromise(currentResult)) { currentResult = Promise.resolve(currentResult); } return currentResult.then((result: any): any => { if (isStream(result) && include) { include.streamPromise.then((result) => { (<Promise<ODataResult>>ODataRequestResult[method](result)).then((result) => { if (elementType) result.elementType = elementType; return this.__appendODataContext(result, elementType || this.ctrl.prototype.elementType, (include || this.resourcePath).includes, select).then(() => { resolve(result); }, reject); }, reject); }, reject); } else if (isStream(result) && (!part.key || !Edm.isMediaEntity(elementType || this.ctrl.prototype.elementType))) { result.on("end", () => resolve(ODataRequestResult[method]())); result.on("error", reject); } else if (!(result instanceof ODataResult)) { return (<Promise<ODataResult>>ODataRequestResult[method](result)).then((result) => { if (!this.streamStart && writeMethods.indexOf(this.method) < 0 && !result.body) return reject(new ResourceNotFoundError()); try { if (elementType) result.elementType = elementType; this.__appendODataContext(result, elementType || this.ctrl.prototype.elementType, (include || this.resourcePath).includes, select).then(() => { if (!this.streamEnd && this.streamEnabled && this.streamStart) this.on("end", () => resolve(result)); else resolve(result); }, reject); } catch (err) { reject(err); } }, reject); } else { try { if (elementType) result.elementType = elementType; this.__appendODataContext(result, elementType || this.ctrl.prototype.elementType, (include || this.resourcePath).includes, select).then(() => { if (!this.streamEnd && this.streamEnabled && this.streamStart) this.on("end", () => resolve(result)); else resolve(result); }, reject); } catch (err) { reject(err); } } }, reject); }catch(err){ reject(err); } }); } private async __deserialize(obj, type) { for (let prop in obj) { try { let propType = Edm.getType(type, prop, this.serverType.container); let fn = Edm.getDeserializer(type, prop, propType, this.serverType.container); if (typeof fn == "function") { obj[prop] = await fn(obj[prop], prop, propType); } else if (typeof obj[prop] == "object") { await this.__deserialize(obj[prop], propType); } } catch (err) { } } } private __stripOData(obj) { for (let prop in obj) { if (prop.indexOf("@odata") >= 0) delete obj[prop]; if (typeof obj[prop] == "object") this.__stripOData(obj[prop]); } } private __EntitySetName(part: NavigationPart): Function { let ctrl = this.entitySets[part.name]; let params = {}; if (part.key) part.key.forEach((key) => params[key.name] = key.value); return (data) => { return this.__read(ctrl, part, params, data); }; } private __actionOrFunctionImport(part: NavigationPart): Function { let fn = this.serverType.prototype[part.name]; return (data) => { return new Promise(async (resolve, reject) => { try { this.__enableStreaming(part); let returnType = <Function>Edm.getReturnType(this.serverType, part.name, this.serverType.container); let isAction = false; let schemas = this.serverType.$metadata().edmx.dataServices.schemas; if (Edm.isActionImport(this.serverType, part.name) || schemas.some(schema => schema.entityContainer.some(container => container.actionImports.some(actionImport => actionImport.name == part.name))) ) { isAction = true; part.params = Object.assign(part.params || {}, this.body || {}); } await this.__applyParams(this.serverType, part.name, part.params); let result = fnCaller.call(data, fn, part.params); if (isIterator(fn)) { result = run(result, defaultHandlers);