import {BundleOptions} from './bundlingCalls/bundleExecutor';
export class RetryOptions {
retryCodes: number[];
backoffSettings: BackoffSettings;
constructor(retryCodes: number[], backoffSettings: BackoffSettings) {
this.retryCodes = retryCodes;
this.backoffSettings = backoffSettings;
}
}
export interface BackoffSettings {
maxRetries?: number;
initialRetryDelayMillis: number;
retryDelayMultiplier: number;
maxRetryDelayMillis: number;
initialRpcTimeoutMillis?: number | null;
maxRpcTimeoutMillis?: number | null;
totalTimeoutMillis?: number | null;
rpcTimeoutMultiplier?: number | null;
}
export interface CallOptions {
timeout?: number;
retry?: RetryOptions | null;
autoPaginate?: boolean;
pageToken?: string;
pageSize?: number;
maxResults?: number;
maxRetries?: number;
otherArgs?: {[index: string]: any};
bundleOptions?: BundleOptions | null;
isBundling?: boolean;
longrunning?: BackoffSettings;
promise?: PromiseConstructor;
}
export class CallSettings {
timeout: number;
retry?: RetryOptions | null;
autoPaginate?: boolean;
pageToken?: string;
pageSize?: number;
maxResults?: number;
otherArgs: {[index: string]: any};
bundleOptions?: BundleOptions | null;
isBundling: boolean;
longrunning?: BackoffSettings;
promise: PromiseConstructor;
constructor(settings?: CallOptions) {
settings = settings || {};
this.timeout = settings.timeout || 30 * 1000;
this.retry = settings.retry;
this.autoPaginate =
'autoPaginate' in settings ? settings.autoPaginate : true;
this.pageToken = settings.pageToken;
this.maxResults = settings.maxResults;
this.otherArgs = settings.otherArgs || {};
this.bundleOptions = settings.bundleOptions;
this.isBundling = 'isBundling' in settings ? settings.isBundling! : true;
this.longrunning =
'longrunning' in settings ? settings.longrunning : undefined;
this.promise = 'promise' in settings ? settings.promise! : Promise;
}
merge(options?: CallOptions | null) {
if (!options) {
return new CallSettings(this);
}
let timeout = this.timeout;
let retry = this.retry;
let autoPaginate = this.autoPaginate;
let pageToken = this.pageToken;
let pageSize = this.pageSize;
let maxResults = this.maxResults;
let otherArgs = this.otherArgs;
let isBundling = this.isBundling;
let longrunning = this.longrunning;
let promise = this.promise;
if ('timeout' in options) {
timeout = options.timeout!;
}
if ('retry' in options) {
retry = options.retry;
}
if ('autoPaginate' in options && !options.autoPaginate) {
autoPaginate = false;
}
if ('pageToken' in options) {
autoPaginate = false;
pageToken = options.pageToken;
}
if ('pageSize' in options) {
pageSize = options.pageSize;
}
if ('maxResults' in options) {
maxResults = options.maxResults;
}
if ('otherArgs' in options) {
otherArgs = {};
for (const key in this.otherArgs) {
otherArgs[key] = this.otherArgs[key];
}
for (const optionsKey in options.otherArgs!) {
otherArgs[optionsKey] = options.otherArgs![optionsKey];
}
}
if ('isBundling' in options) {
isBundling = options.isBundling!;
}
if ('maxRetries' in options) {
retry!.backoffSettings!.maxRetries = options.maxRetries;
delete retry!.backoffSettings!.totalTimeoutMillis;
}
if ('longrunning' in options) {
longrunning = options.longrunning;
}
if ('promise' in options) {
promise = options.promise!;
}
return new CallSettings({
timeout,
retry,
bundleOptions: this.bundleOptions,
longrunning,
autoPaginate,
pageToken,
pageSize,
maxResults,
otherArgs,
isBundling,
promise,
});
}
}
export function createRetryOptions(
retryCodes: number[],
backoffSettings: BackoffSettings
): RetryOptions {
return {
retryCodes,
backoffSettings,
};
}
export function createBackoffSettings(
initialRetryDelayMillis: number,
retryDelayMultiplier: number,
maxRetryDelayMillis: number,
initialRpcTimeoutMillis: number | null,
rpcTimeoutMultiplier: number | null,
maxRpcTimeoutMillis: number | null,
totalTimeoutMillis: number | null
): BackoffSettings {
return {
initialRetryDelayMillis,
retryDelayMultiplier,
maxRetryDelayMillis,
initialRpcTimeoutMillis,
rpcTimeoutMultiplier,
maxRpcTimeoutMillis,
totalTimeoutMillis,
};
}
export function createDefaultBackoffSettings() {
return createBackoffSettings(100, 1.3, 60000, null, null, null, null);
}
export function createMaxRetriesBackoffSettings(
initialRetryDelayMillis: number,
retryDelayMultiplier: number,
maxRetryDelayMillis: number,
initialRpcTimeoutMillis: number,
rpcTimeoutMultiplier: number,
maxRpcTimeoutMillis: number,
maxRetries: number
): BackoffSettings {
return {
initialRetryDelayMillis,
retryDelayMultiplier,
maxRetryDelayMillis,
initialRpcTimeoutMillis,
rpcTimeoutMultiplier,
maxRpcTimeoutMillis,
maxRetries,
};
}
export function createBundleOptions(options: BundlingConfig): BundleOptions {
const params = [
'element_count_threshold',
'element_count_limit',
'request_byte_threshold',
'request_byte_limit',
'delay_threshold_millis',
];
params.forEach(param => {
if (param in options && typeof options[param] !== 'number') {
throw new Error(`${param} should be a number`);
}
});
const elementCountThreshold = options.element_count_threshold || 0;
const elementCountLimit = options.element_count_limit || 0;
const requestByteThreshold = options.request_byte_threshold || 0;
const requestByteLimit = options.request_byte_limit || 0;
const delayThreshold = options.delay_threshold_millis || 0;
if (
elementCountThreshold === 0 &&
requestByteThreshold === 0 &&
delayThreshold === 0
) {
throw new Error('one threshold should be > 0');
}
return {
elementCountThreshold,
elementCountLimit,
requestByteThreshold,
requestByteLimit,
delayThreshold,
};
}
function constructRetry(
methodConfig: MethodConfig,
retryCodes: {[index: string]: string[]},
retryParams: {[index: string]: {}},
retryNames: {[index: string]: {}}
): RetryOptions | null | undefined {
if (!methodConfig) {
return null;
}
let codes: number[] | null = null;
if (retryCodes && 'retry_codes_name' in methodConfig) {
const retryCodesName = methodConfig['retry_codes_name'];
codes = (retryCodes[retryCodesName] || []).map(name => {
return Number(retryNames[name]);
});
}
let backoffSettings: BackoffSettings | null = null;
if (retryParams && 'retry_params_name' in methodConfig) {
const params = retryParams[
methodConfig.retry_params_name
] as RetryParamsConfig;
backoffSettings = createBackoffSettings(
params.initial_retry_delay_millis,
params.retry_delay_multiplier,
params.max_retry_delay_millis,
params.initial_rpc_timeout_millis,
params.rpc_timeout_multiplier,
params.max_rpc_timeout_millis,
params.total_timeout_millis
);
}
return createRetryOptions(codes!, backoffSettings!);
}
function mergeRetryOptions(
retry: RetryOptions,
overrides: RetryOptions
): RetryOptions | null {
if (!overrides) {
return null;
}
if (!overrides.retryCodes && !overrides.backoffSettings) {
return retry;
}
let codes = retry.retryCodes;
if (overrides.retryCodes) {
codes = overrides.retryCodes;
}
let backoffSettings = retry.backoffSettings;
if (overrides.backoffSettings) {
backoffSettings = overrides.backoffSettings;
}
return createRetryOptions(codes, backoffSettings);
}
export interface ServiceConfig {
retry_codes: {[index: string]: string[]};
retry_params: {[index: string]: RetryParamsConfig};
methods: {[index: string]: MethodConfig};
}
export interface RetryParamsConfig {
initial_retry_delay_millis: number;
retry_delay_multiplier: number;
max_retry_delay_millis: number;
initial_rpc_timeout_millis: number;
rpc_timeout_multiplier: number;
max_rpc_timeout_millis: number;
total_timeout_millis: number;
}
export interface MethodConfig {
retry_codes_name: string;
retry_params_name: string;
bundling?: BundlingConfig;
timeout_millis?: number;
}
export interface BundlingConfig {
[index: string]: number;
element_count_threshold: number;
element_count_limit: number;
request_byte_threshold: number;
request_byte_limit: number;
delay_threshold_millis: number;
}
export interface ClientConfig {
interfaces?: {[index: string]: ServiceConfig};
}
export function constructSettings(
serviceName: string,
clientConfig: ClientConfig,
configOverrides: ClientConfig,
retryNames: {},
otherArgs?: {},
promise?: PromiseConstructor
) {
otherArgs = otherArgs || {};
const defaults: any = {};
const serviceConfig = (clientConfig.interfaces || {})[serviceName];
if (!serviceConfig) {
return null;
}
const overrides = (configOverrides.interfaces || {})[serviceName] || {};
const methods = serviceConfig.methods;
const overridingMethods = overrides.methods || {};
for (const methodName in methods) {
const methodConfig = methods[methodName];
const jsName = methodName[0].toLowerCase() + methodName.slice(1);
let retry = constructRetry(
methodConfig,
serviceConfig.retry_codes,
serviceConfig.retry_params,
retryNames
);
let bundlingConfig = methodConfig.bundling;
let timeout = methodConfig.timeout_millis;
if (methodName in overridingMethods) {
const overridingMethod = overridingMethods[methodName];
if (overridingMethod) {
if ('bundling' in overridingMethod) {
bundlingConfig = overridingMethod.bundling;
}
if ('timeout_millis' in overridingMethod) {
timeout = overridingMethod.timeout_millis;
}
}
retry = mergeRetryOptions(
retry!,
constructRetry(
overridingMethod,
overrides.retry_codes,
overrides.retry_params,
retryNames
)!
);
}
defaults[jsName] = new CallSettings({
timeout,
retry,
bundleOptions: bundlingConfig
? createBundleOptions(bundlingConfig)
: null,
otherArgs,
promise: promise || Promise,
});
}
return defaults;
}