Skip to content

Commit

Permalink
Improve organization of context object
Browse files Browse the repository at this point in the history
Remove backwards compat sys data
Rename bindingMetadata to triggerMetadata
Remove executionContext and move functionName/retryContext up a level
Remove bindingDefinitions because users will define it in code

Related to Azure/azure-functions-nodejs-worker#204
  • Loading branch information
ejizba committed Aug 19, 2022
1 parent 0aac975 commit 15e0d38
Show file tree
Hide file tree
Showing 11 changed files with 448 additions and 717 deletions.
61 changes: 17 additions & 44 deletions src/Context.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,10 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License.

import {
BindingDefinition,
Context,
ContextBindingData,
ContextBindings,
ExecutionContext,
Logger,
TraceContext,
} from '@azure/functions';
import { Context, ContextBindings, Logger, RetryContext, TraceContext, TriggerMetadata } from '@azure/functions';
import { RpcInvocationRequest, RpcLog, RpcParameterBinding } from '@azure/functions-core';
import { v4 as uuid } from 'uuid';
import {
convertKeysToCamelCase,
getBindingDefinitions,
getNormalizedBindingData,
} from './converters/BindingConverters';
import { fromRpcTraceContext, fromTypedData } from './converters/RpcConverters';
import { convertKeysToCamelCase } from './converters/convertKeysToCamelCase';
import { fromRpcRetryContext, fromRpcTraceContext, fromTypedData } from './converters/RpcConverters';
import { FunctionInfo } from './FunctionInfo';
import { Request } from './http/Request';
import { Response } from './http/Response';
Expand Down Expand Up @@ -55,20 +42,8 @@ export function CreateContextAndInputs(
if (httpInput) {
context.req = httpInput;
context.res = new Response();
// 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: uuid(),
};
// 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,
Expand All @@ -77,25 +52,26 @@ export function CreateContextAndInputs(

class InvocationContext implements Context {
invocationId: string;
executionContext: ExecutionContext;
functionName: string;
bindings: ContextBindings;
bindingData: ContextBindingData;
traceContext: TraceContext;
bindingDefinitions: BindingDefinition[];
triggerMetadata: TriggerMetadata;
traceContext?: TraceContext;
retryContext?: RetryContext;
log: Logger;
req?: Request;
res?: Response;

constructor(info: FunctionInfo, request: RpcInvocationRequest, userLogCallback: UserLogCallback) {
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.functionName = info.name;
this.triggerMetadata = request.triggerMetadata ? convertKeysToCamelCase(request.triggerMetadata) : {};
if (request.retryContext) {
this.retryContext = fromRpcRetryContext(request.retryContext);
}
if (request.traceContext) {
this.traceContext = fromRpcTraceContext(request.traceContext);
}

this.bindings = {};

// Log message that is tied to function invocation
Expand All @@ -105,9 +81,6 @@ class InvocationContext implements Context {
info: (...args: any[]) => userLogCallback(RpcLog.Level.Information, ...args),
verbose: (...args: any[]) => userLogCallback(RpcLog.Level.Trace, ...args),
});

this.bindingData = getNormalizedBindingData(request);
this.bindingDefinitions = getBindingDefinitions(info);
}
}

Expand Down
66 changes: 0 additions & 66 deletions src/converters/BindingConverters.ts

This file was deleted.

42 changes: 29 additions & 13 deletions src/converters/RpcConverters.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License.

import { TraceContext } from '@azure/functions';
import { Exception, RetryContext, TraceContext } from '@azure/functions';
import {
RpcException,
RpcNullableBool,
RpcNullableDouble,
RpcNullableString,
RpcNullableTimestamp,
RpcRetryContext,
RpcTraceContext,
RpcTypedData,
} from '@azure/functions-core';
import { isLong } from 'long';
import { InternalException } from '../utils/InternalException';
import { copyPropIfDefined, nonNullProp } from '../utils/nonNull';

/**
* Converts 'ITypedData' input from the RPC layer to JavaScript types.
Expand Down Expand Up @@ -46,20 +49,33 @@ export function fromTypedData(typedData?: RpcTypedData, convertStringToJson = tr
}
}

/**
* Converts 'IRpcTraceContext' input from RPC layer to dictionary of key value pairs.
* @param traceContext IRpcTraceContext object containing the activityId, tracestate and attributes.
*/
export function fromRpcTraceContext(traceContext: RpcTraceContext | null | undefined): TraceContext {
if (traceContext) {
return <TraceContext>{
traceparent: traceContext.traceParent,
tracestate: traceContext.traceState,
attributes: traceContext.attributes,
};
export function fromRpcRetryContext(retryContext: RpcRetryContext): RetryContext {
const result: RetryContext = {
retryCount: nonNullProp(retryContext, 'retryCount'),
maxRetryCount: nonNullProp(retryContext, 'maxRetryCount'),
};
if (retryContext.exception) {
result.exception = fromRpcException(retryContext.exception);
}
return result;
}

return <TraceContext>{};
function fromRpcException(exception: RpcException): Exception {
const result: Exception = {};
copyPropIfDefined(exception, result, 'message');
copyPropIfDefined(exception, result, 'source');
copyPropIfDefined(exception, result, 'stackTrace');
return result;
}

export function fromRpcTraceContext(traceContext: RpcTraceContext): TraceContext {
const result: TraceContext = {};
copyPropIfDefined(traceContext, result, 'traceParent');
copyPropIfDefined(traceContext, result, 'traceState');
if (traceContext.attributes) {
result.attributes = traceContext.attributes;
}
return result;
}

/**
Expand Down
25 changes: 25 additions & 0 deletions src/converters/convertKeysToCamelCase.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License.

import { fromTypedData } from './RpcConverters';

// Recursively convert keys of objects to camel case
export function convertKeysToCamelCase(obj: any): { [key: string]: any } {
const output = {};
for (const key in obj) {
// Only "undefined" will be replaced with original object property. For example:
//{ string : "0" } -> 0
//{ string : "false" } -> false
//"test" -> "test" (undefined returned from fromTypedData)
const valueFromDataType = fromTypedData(obj[key]);
const value = valueFromDataType === undefined ? obj[key] : valueFromDataType;
const camelCasedKey = key.charAt(0).toLocaleLowerCase() + key.slice(1);
// If the value is a JSON object (and not array and not http, which is already cased), convert keys to camel case
if (!Array.isArray(value) && typeof value === 'object' && value && value.http == undefined) {
output[camelCasedKey] = convertKeysToCamelCase(value);
} else {
output[camelCasedKey] = value;
}
}
return output;
}
6 changes: 6 additions & 0 deletions src/utils/nonNull.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,9 @@ export function nonNullValue<T>(value: T | undefined, propertyNameOrMessage?: st

return value;
}

export function copyPropIfDefined<TData, TKey extends keyof TData>(source: TData, destination: TData, key: TKey): void {
if (source[key] !== null && source[key] !== undefined) {
destination[key] = source[key];
}
}
Loading

0 comments on commit 15e0d38

Please sign in to comment.