Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for binding provider #84

Closed
wants to merge 12 commits into from
Closed
9 changes: 6 additions & 3 deletions src/steps/composed/executors/loop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { StepExecutionError } from '../../errors';
import { ExecutionBindings } from '../../../workflow/types';
import { ComposableStepExecutor } from './composable';
import { StepExecutor, StepOutput } from '../../types';
import { ErrorUtils } from '../../../common';

export class LoopStepExecutor extends ComposableStepExecutor {
constructor(nextExecutor: StepExecutor) {
Expand All @@ -15,11 +16,13 @@ export class LoopStepExecutor extends ComposableStepExecutor {
try {
return await super.execute(element, executionBindings);
} catch (error: any) {
const stepExecutionError = ErrorUtils.createStepExecutionError(error, this.getStepName());
return {
error: {
message: error.message,
status: error.status,
originalError: error.originalError,
message: stepExecutionError.message,
status: stepExecutionError.status,
error: stepExecutionError,
originalError: stepExecutionError.originalError,
},
};
}
Expand Down
8 changes: 4 additions & 4 deletions src/steps/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ export class StepCreationError extends StatusError {
export class StepExecutionError extends StatusError {
stepName: string;
childStepName?: string;
error?: Error;
originalError?: Error;
error: Error;
originalError: Error;
constructor(
message: string,
status: number,
Expand All @@ -25,8 +25,8 @@ export class StepExecutionError extends StatusError {
super(message, status);
this.stepName = stepName;
this.childStepName = childStepName;
this.error = error;
this.originalError = (error as any)?.originalError || error;
this.error = error || this;
this.originalError = (this.error as any).originalError || error;
}
}

Expand Down
4 changes: 3 additions & 1 deletion src/steps/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ExecutionBindings, Binding } from '../workflow/types';
import { Executor } from '../common/types';
import { JsonataStepExecutor, JsonTemplateStepExecutor } from './base/simple/executors/template';
import { StepExecutionError } from './errors';

export interface StepExecutor extends Executor {
/**
Expand All @@ -21,7 +22,8 @@ export type StepOutput = {
error?: {
message: string;
status: number;
originalError?: Error;
originalError: Error;
error: StepExecutionError;
};
skipped?: boolean;
output?: any;
Expand Down
5 changes: 5 additions & 0 deletions src/workflow/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export type WorkflowOptions = {
creationTimeBindings?: Record<string, any>;
templateType?: TemplateType;
executor?: WorkflowExecutor;
bindingProvider?: WorkflowBindingProvider;
};

export type WorkflowOptionsInternal = WorkflowOptions & {
Expand All @@ -66,3 +67,7 @@ export type WorkflowOptionsInternal = WorkflowOptions & {
export interface WorkflowExecutor {
execute(engine: WorkflowEngine, input: any): Promise<WorkflowOutput>;
}

export interface WorkflowBindingProvider {
provide(name: string): Promise<any>;
}
29 changes: 20 additions & 9 deletions src/workflow/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
PathBinding,
ValueBinding,
Workflow,
WorkflowBindingProvider,
WorkflowExecutor,
WorkflowOptionsInternal,
} from './types';
Expand Down Expand Up @@ -55,13 +56,26 @@ export class WorkflowUtils {
}
}

private static async getModuleExportsFromProvider(
modulePath: string,
provider: WorkflowBindingProvider,
): Promise<any> {
try {
return await provider.provide(modulePath);
} catch (error: any) {
// Ignoring error
}
}

private static async getModuleExportsFromAllPaths(
rootPath: string,
bindingPath: string,
options: WorkflowOptionsInternal,
): Promise<any> {
return (
(await this.getModuleExports(bindingPath)) ||
(await this.getModuleExports(path.join(rootPath, bindingPath), true))
(options.bindingProvider
? await this.getModuleExportsFromProvider(bindingPath, options.bindingProvider)
: await this.getModuleExports(bindingPath)) ??
(await this.getModuleExports(path.join(options.rootPath, bindingPath), true))
);
}

Expand All @@ -74,7 +88,7 @@ export class WorkflowUtils {

const bindings = await Promise.all(
options.bindingsPaths.map(async (bindingPath) => {
return this.getModuleExportsFromAllPaths(options.rootPath, bindingPath);
return this.getModuleExportsFromAllPaths(bindingPath, options);
}),
);
return Object.assign({}, ...bindings);
Expand Down Expand Up @@ -104,8 +118,8 @@ export class WorkflowUtils {

const pathBinding = binding as PathBinding;
const bindingSource = await this.getModuleExportsFromAllPaths(
options.rootPath,
pathBinding.path || 'bindings',
options,
);
if (pathBinding.name) {
bindingsObj[pathBinding.name] = pathBinding.exportAll
Expand All @@ -123,10 +137,7 @@ export class WorkflowUtils {
options: WorkflowOptionsInternal,
): Promise<WorkflowExecutor> {
if (workflow?.executor?.path) {
let executor = await this.getModuleExportsFromAllPaths(
options.rootPath,
workflow.executor.path,
);
let executor = await this.getModuleExportsFromAllPaths(workflow.executor.path, options);

if (
!executor ||
Expand Down
3 changes: 2 additions & 1 deletion test/e2e-custom.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ describe('Custom Scenarios tests', () => {
},
},
]);
expect(result.output[0].error?.originalError?.message).toEqual('some error');
expect(result.output[0].error?.error.message).toEqual('some error');
expect(result.output[0].error?.originalError.message).toEqual('some error');
});
});
});
6 changes: 3 additions & 3 deletions test/e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ describe('Scenarios tests', () => {
const result = await ScenarioUtils.executeScenario(workflowEngine, scenario);
expect(result.output).toEqual(scenario.output);
} catch (error: any) {
expect(error).toMatchObject(CommonUtils.getErrorMatcher(scenario.error));
if (scenario.errorClass) {
expect(error.error?.constructor.name).toEqual(scenario.errorClass);
CommonUtils.matchError(error, scenario.error);
if (scenario.error?.errorClass) {
expect(error.error?.constructor.name).toEqual(scenario.error.errorClass);
}
} finally {
if (scenario.logLevel !== undefined) {
Expand Down
1 change: 1 addition & 0 deletions test/scenarios/bindings_provider/bindings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const anotherMessage = 'Got binding from normal binding.';
11 changes: 11 additions & 0 deletions test/scenarios/bindings_provider/data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { BindingProvider } from './provider';
import { Scenario } from '../../types';

export const data = [
{
output: 'Got binding from provider.Got binding from normal binding.',
options: {
bindingProvider: BindingProvider.INSTANCE,
},
},
] as Scenario[];
10 changes: 10 additions & 0 deletions test/scenarios/bindings_provider/provider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { WorkflowBindingProvider } from '../../../src';
export class BindingProvider implements WorkflowBindingProvider {
static readonly INSTANCE = new BindingProvider();
provide(name: string): Promise<any> {
if (name == 'message') {
return Promise.resolve({ message: 'Got binding from provider.' });
}
return Promise.reject(new Error('Binding not found'));
}
}
9 changes: 9 additions & 0 deletions test/scenarios/bindings_provider/workflow.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
bindings:
# this will be resolved using custom binding provider
- path: message
- name: anotherMessage

steps:
- name: getMessage
template: |
$message & $anotherMessage
2 changes: 1 addition & 1 deletion test/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export type ScenarioError = {
status?: string;
stepName?: string;
childStepName?: string;
errorClass?: string;
};

export type Scenario = {
Expand All @@ -15,6 +16,5 @@ export type Scenario = {
stepName?: string;
output?: any;
error?: ScenarioError;
errorClass?: string;
logLevel?: LogLevel;
};
19 changes: 13 additions & 6 deletions test/utils/common.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
import { ScenarioError } from '../types';

export class CommonUtils {
static getErrorMatcher(error?: ScenarioError) {
if (!error) {
static matchError(actual: any, expected?: ScenarioError) {
if (!expected) {
// Ideally shouldn't reach here.
// Sending default error so that test case fails.
return { message: 'should fail' };
}
let errorMatcher = error;
if (error.message) {
errorMatcher.message = expect.stringContaining(error.message);
if (expected.message) {
expect(actual.message).toEqual(expect.stringContaining(expected.message));
}
if (expected.status) {
expect(actual.status).toEqual(expected.status);
}
if (expected.stepName) {
expect(actual.stepName).toEqual(expected.stepName);
}
if (expected.childStepName) {
expect(actual.childStepName).toEqual(expected.childStepName);
}
return errorMatcher;
}
}