diff --git a/src/steps/composed/executors/loop.ts b/src/steps/composed/executors/loop.ts index c2af6b9..0bf6bf4 100644 --- a/src/steps/composed/executors/loop.ts +++ b/src/steps/composed/executors/loop.ts @@ -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) { @@ -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, }, }; } diff --git a/src/steps/errors.ts b/src/steps/errors.ts index 2ee760a..a5c3808 100644 --- a/src/steps/errors.ts +++ b/src/steps/errors.ts @@ -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, @@ -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; } } diff --git a/src/steps/types.ts b/src/steps/types.ts index 26287ad..0d51ce5 100644 --- a/src/steps/types.ts +++ b/src/steps/types.ts @@ -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 { /** @@ -21,7 +22,8 @@ export type StepOutput = { error?: { message: string; status: number; - originalError?: Error; + originalError: Error; + error: StepExecutionError; }; skipped?: boolean; output?: any; diff --git a/src/workflow/types.ts b/src/workflow/types.ts index f1f4660..c73d30a 100644 --- a/src/workflow/types.ts +++ b/src/workflow/types.ts @@ -56,6 +56,7 @@ export type WorkflowOptions = { creationTimeBindings?: Record; templateType?: TemplateType; executor?: WorkflowExecutor; + bindingProvider?: WorkflowBindingProvider; }; export type WorkflowOptionsInternal = WorkflowOptions & { @@ -66,3 +67,7 @@ export type WorkflowOptionsInternal = WorkflowOptions & { export interface WorkflowExecutor { execute(engine: WorkflowEngine, input: any): Promise; } + +export interface WorkflowBindingProvider { + provide(name: string): Promise; +} diff --git a/src/workflow/utils.ts b/src/workflow/utils.ts index 8fdfeaf..98af24e 100644 --- a/src/workflow/utils.ts +++ b/src/workflow/utils.ts @@ -8,6 +8,7 @@ import { PathBinding, ValueBinding, Workflow, + WorkflowBindingProvider, WorkflowExecutor, WorkflowOptionsInternal, } from './types'; @@ -55,13 +56,26 @@ export class WorkflowUtils { } } + private static async getModuleExportsFromProvider( + modulePath: string, + provider: WorkflowBindingProvider, + ): Promise { + try { + return await provider.provide(modulePath); + } catch (error: any) { + // Ignoring error + } + } + private static async getModuleExportsFromAllPaths( - rootPath: string, bindingPath: string, + options: WorkflowOptionsInternal, ): Promise { 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)) ); } @@ -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); @@ -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 @@ -123,10 +137,7 @@ export class WorkflowUtils { options: WorkflowOptionsInternal, ): Promise { 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 || diff --git a/test/e2e-custom.test.ts b/test/e2e-custom.test.ts index a0bb62f..aafa6a6 100644 --- a/test/e2e-custom.test.ts +++ b/test/e2e-custom.test.ts @@ -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'); }); }); }); diff --git a/test/e2e.test.ts b/test/e2e.test.ts index 42d5094..0bb3c67 100644 --- a/test/e2e.test.ts +++ b/test/e2e.test.ts @@ -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) { diff --git a/test/scenarios/bindings_provider/bindings.ts b/test/scenarios/bindings_provider/bindings.ts new file mode 100644 index 0000000..063a72b --- /dev/null +++ b/test/scenarios/bindings_provider/bindings.ts @@ -0,0 +1 @@ +export const anotherMessage = 'Got binding from normal binding.'; diff --git a/test/scenarios/bindings_provider/data.ts b/test/scenarios/bindings_provider/data.ts new file mode 100644 index 0000000..366c7a4 --- /dev/null +++ b/test/scenarios/bindings_provider/data.ts @@ -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[]; diff --git a/test/scenarios/bindings_provider/provider.ts b/test/scenarios/bindings_provider/provider.ts new file mode 100644 index 0000000..d786a1e --- /dev/null +++ b/test/scenarios/bindings_provider/provider.ts @@ -0,0 +1,10 @@ +import { WorkflowBindingProvider } from '../../../src'; +export class BindingProvider implements WorkflowBindingProvider { + static readonly INSTANCE = new BindingProvider(); + provide(name: string): Promise { + if (name == 'message') { + return Promise.resolve({ message: 'Got binding from provider.' }); + } + return Promise.reject(new Error('Binding not found')); + } +} diff --git a/test/scenarios/bindings_provider/workflow.yaml b/test/scenarios/bindings_provider/workflow.yaml new file mode 100644 index 0000000..9ee955e --- /dev/null +++ b/test/scenarios/bindings_provider/workflow.yaml @@ -0,0 +1,9 @@ +bindings: + # this will be resolved using custom binding provider + - path: message + - name: anotherMessage + +steps: + - name: getMessage + template: | + $message & $anotherMessage diff --git a/test/types.ts b/test/types.ts index 5b1ca40..2e24d56 100644 --- a/test/types.ts +++ b/test/types.ts @@ -5,6 +5,7 @@ export type ScenarioError = { status?: string; stepName?: string; childStepName?: string; + errorClass?: string; }; export type Scenario = { @@ -15,6 +16,5 @@ export type Scenario = { stepName?: string; output?: any; error?: ScenarioError; - errorClass?: string; logLevel?: LogLevel; }; diff --git a/test/utils/common.ts b/test/utils/common.ts index 46208af..03c419d 100644 --- a/test/utils/common.ts +++ b/test/utils/common.ts @@ -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; } }