@azure/functions
Version:
Microsoft Azure Functions NodeJS Framework
138 lines (124 loc) • 4.9 kB
text/typescript
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License.
import {
BindingDefinition,
Context,
ContextBindingData,
ContextBindings,
ExecutionContext,
Logger,
TraceContext,
} from '@azure/functions';
import { RpcInvocationRequest, RpcLogLevel, RpcParameterBinding } from '@azure/functions-core';
import { randomUUID } from 'crypto';
import {
convertKeysToCamelCase,
getBindingDefinitions,
getNormalizedBindingData,
} from './converters/BindingConverters';
import { fromRpcTraceContext, fromTypedData } from './converters/RpcConverters';
import { FunctionInfo } from './FunctionInfo';
import { Request } from './http/Request';
import { Response } from './http/Response';
import EventEmitter = require('events');
export function CreateContextAndInputs(
info: FunctionInfo,
request: RpcInvocationRequest,
userLogCallback: UserLogCallback,
doneEmitter: EventEmitter
) {
const context = new InvocationContext(info, request, userLogCallback, doneEmitter);
const bindings: ContextBindings = {};
const inputs: any[] = [];
let httpInput: Request | undefined;
for (const binding of <RpcParameterBinding[]>request.inputData) {
if (binding.data && binding.name) {
let input;
if (binding.data && binding.data.http) {
input = httpInput = new Request(binding.data.http);
} else {
// TODO: Don't hard code fix for camelCase https://github.com/Azure/azure-functions-nodejs-worker/issues/188
if (info.getTimerTriggerName() === binding.name) {
// v2 worker converts timer trigger object to camelCase
input = convertKeysToCamelCase(binding)['data'];
} else {
input = fromTypedData(binding.data);
}
}
bindings[binding.name] = input;
inputs.push(input);
}
}
context.bindings = bindings;
if (httpInput) {
context.req = httpInput;
context.res = new Response(context.done);
// This is added for backwards compatability with what the host used to send to the worker
context.bindingData.sys = {
methodName: info.name,
utcNow: new Date().toISOString(),
randGuid: randomUUID(),
};
// Populate from HTTP request for backwards compatibility if missing
if (!context.bindingData.query) {
context.bindingData.query = Object.assign({}, httpInput.query);
}
if (!context.bindingData.headers) {
context.bindingData.headers = Object.assign({}, httpInput.headers);
}
}
return {
context: <Context>context,
inputs: inputs,
};
}
class InvocationContext implements Context {
invocationId: string;
executionContext: ExecutionContext;
bindings: ContextBindings;
bindingData: ContextBindingData;
traceContext: TraceContext;
bindingDefinitions: BindingDefinition[];
log: Logger;
req?: Request;
res?: Response;
done: DoneCallback;
constructor(
info: FunctionInfo,
request: RpcInvocationRequest,
userLogCallback: UserLogCallback,
doneEmitter: EventEmitter
) {
this.invocationId = <string>request.invocationId;
this.traceContext = fromRpcTraceContext(request.traceContext);
const executionContext = <ExecutionContext>{
invocationId: this.invocationId,
functionName: info.name,
functionDirectory: info.directory,
retryContext: request.retryContext,
};
this.executionContext = executionContext;
this.bindings = {};
// Log message that is tied to function invocation
this.log = Object.assign((...args: any[]) => userLogCallback('information', ...args), {
error: (...args: any[]) => userLogCallback('error', ...args),
warn: (...args: any[]) => userLogCallback('warning', ...args),
info: (...args: any[]) => userLogCallback('information', ...args),
verbose: (...args: any[]) => userLogCallback('trace', ...args),
});
this.bindingData = getNormalizedBindingData(request);
this.bindingDefinitions = getBindingDefinitions(info);
this.done = (err?: unknown, result?: any) => {
doneEmitter.emit('done', err, result);
};
}
}
export interface InvocationResult {
return: any;
bindings: ContextBindings;
}
export type DoneCallback = (err?: unknown, result?: any) => void;
export type UserLogCallback = (level: RpcLogLevel, ...args: any[]) => void;
export interface Dict<T> {
[key: string]: T;
}