From 12849ce5b86faee09135466c44b66254de7af74b Mon Sep 17 00:00:00 2001 From: Dilip Kola Date: Wed, 12 Jun 2024 14:47:40 +0530 Subject: [PATCH] refactor: engine and add more utilites --- readme.md | 23 ++++++++++++--------- src/engine.ts | 41 +++++++++++++++++++++++++------------ src/types.ts | 2 +- test/e2e.test.ts | 3 +-- test/scenario.test.ts | 3 +-- test/scenarios/base/data.ts | 8 ++++++++ test/utils/scenario.ts | 28 +++++++++++++++++-------- 7 files changed, 72 insertions(+), 36 deletions(-) create mode 100644 test/scenarios/base/data.ts diff --git a/readme.md b/readme.md index ae78f8c..0368a54 100644 --- a/readme.md +++ b/readme.md @@ -59,6 +59,18 @@ This library generates a javascript function code from the template and then use [Engine](src/engine.ts) class abstracts the above steps and provides a convenient way to use the json templates to evaluate the inputs. +## Getting started + +### Use npm package + +`npm install @rudderstack/json-template-engine` + +```ts +const { JsonTemplateEngine } = require('@rudderstack/json-template-engine'); +const engine = JsonTemplateEngine.create(`'Hello ' + .name`); +engine.evaluate({ name: 'World' }); // => 'Hello World' +``` + ## Features Template is a set of statements and result the last statement is the output of the template. @@ -376,6 +388,7 @@ JsonTemplateEngine.create(`let a = {{$.a.b.c}};`, { We can use compile time expressions to generate a template and then recompile it as expression. Refer these examples [simple compilation](test/scenarios/compile_time_expressions/template.jt) and [complex compilation](test/scenarios/compile_time_expressions/two_level_path_processing.jt) for more details. + ### Comments Supports both c style single line (`//`) and block comments (`/* .. */`). @@ -383,16 +396,6 @@ Refer this [example](test/scenarios/comments/template.jt) for more details. For more examples, refer [Scenarios](test/scenarios) -## Getting started - -`npm install @rudderstack/json-template-engine` - -```ts -const { JsonTemplateEngine } = require('@rudderstack/json-template-engine'); -const engine = JsonTemplateEngine.create(`'Hello ' + .name`); -engine.evaluate({ name: 'World' }); // => 'Hello World' -``` - ## Testing `npm test` diff --git a/src/engine.ts b/src/engine.ts index fe880d7..3f9e2a2 100644 --- a/src/engine.ts +++ b/src/engine.ts @@ -1,5 +1,5 @@ /* eslint-disable import/no-cycle */ -import { BINDINGS_PARAM_KEY, DATA_PARAM_KEY } from './constants'; +import { BINDINGS_PARAM_KEY, DATA_PARAM_KEY, EMPTY_EXPR } from './constants'; import { JsonTemplateLexer } from './lexer'; import { JsonTemplateParser } from './parser'; import { JsonTemplateReverseTranslator } from './reverse_translator'; @@ -14,15 +14,12 @@ export class JsonTemplateEngine { this.fn = fn; } - private static compileAsSync( - templateOrExpr: string | Expression, - options?: EngineOptions, - ): Function { + private static compileAsSync(template: TemplateInput, options?: EngineOptions): Function { // eslint-disable-next-line @typescript-eslint/no-implied-eval return Function( DATA_PARAM_KEY, BINDINGS_PARAM_KEY, - JsonTemplateEngine.translate(templateOrExpr, options), + JsonTemplateEngine.translate(template, options), ); } @@ -55,14 +52,14 @@ export class JsonTemplateEngine { return new JsonTemplateEngine(JsonTemplateEngine.compileAsAsync(templateOrExpr, options)); } - static createAsSync( - templateOrExpr: string | Expression, - options?: EngineOptions, - ): JsonTemplateEngine { - return new JsonTemplateEngine(JsonTemplateEngine.compileAsSync(templateOrExpr, options)); + static createAsSync(template: TemplateInput, options?: EngineOptions): JsonTemplateEngine { + return new JsonTemplateEngine(JsonTemplateEngine.compileAsSync(template, options)); } static parse(template: TemplateInput, options?: EngineOptions): Expression { + if (!template) { + return EMPTY_EXPR; + } if (isExpression(template)) { return template as Expression; } @@ -90,7 +87,25 @@ export class JsonTemplateEngine { ); } - evaluate(data: unknown, bindings: Record = {}): unknown { - return this.fn(data ?? {}, bindings); + static evaluateAsSync( + template: TemplateInput, + options: EngineOptions = {}, + data: unknown = {}, + bindings: Record = {}, + ): unknown { + return JsonTemplateEngine.createAsSync(template, options).evaluate(data, bindings); + } + + static evaluate( + template: TemplateInput, + options: EngineOptions = {}, + data: unknown = {}, + bindings: Record = {}, + ): unknown { + return JsonTemplateEngine.create(template, options).evaluate(data, bindings); + } + + evaluate(data: unknown = {}, bindings: Record = {}): unknown { + return this.fn(data, bindings); } } diff --git a/src/types.ts b/src/types.ts index b2d6d37..0e12c3b 100644 --- a/src/types.ts +++ b/src/types.ts @@ -285,4 +285,4 @@ export type FlatMappingAST = FlatMappingPaths & { outputExpr: PathExpression; }; -export type TemplateInput = string | Expression | FlatMappingPaths[]; +export type TemplateInput = string | Expression | FlatMappingPaths[] | undefined; diff --git a/test/e2e.test.ts b/test/e2e.test.ts index ed88b5d..2e7cde9 100644 --- a/test/e2e.test.ts +++ b/test/e2e.test.ts @@ -24,8 +24,7 @@ describe('Scenarios tests', () => { scenarios.forEach((scenario, index) => { it(`Scenario ${index}: ${Scenario.getTemplatePath(scenario)}`, async () => { try { - const templateEngine = ScenarioUtils.createTemplateEngine(scenarioDir, scenario); - const result = await ScenarioUtils.evaluateScenario(templateEngine, scenario); + const result = await ScenarioUtils.evaluateScenario(scenarioDir, scenario); expect(result).toEqual(scenario.output); } catch (error: any) { expect(error.message).toContain(scenario.error); diff --git a/test/scenario.test.ts b/test/scenario.test.ts index 508e556..a741757 100644 --- a/test/scenario.test.ts +++ b/test/scenario.test.ts @@ -27,8 +27,7 @@ describe(`${scenarioName}:`, () => { scenario.templatePath || 'template.jt' }`, ); - const templateEngine = ScenarioUtils.createTemplateEngine(scenarioDir, scenario); - result = await ScenarioUtils.evaluateScenario(templateEngine, scenario); + result = await ScenarioUtils.evaluateScenario(scenarioDir, scenario); expect(result).toEqual(scenario.output); } catch (error: any) { console.error(error); diff --git a/test/scenarios/base/data.ts b/test/scenarios/base/data.ts new file mode 100644 index 0000000..429d44a --- /dev/null +++ b/test/scenarios/base/data.ts @@ -0,0 +1,8 @@ +import { Scenario } from '../../types'; + +export const data: Scenario[] = [ + { + template: '', + output: undefined, + }, +]; diff --git a/test/utils/scenario.ts b/test/utils/scenario.ts index 7f6c930..db2d506 100644 --- a/test/utils/scenario.ts +++ b/test/utils/scenario.ts @@ -3,11 +3,14 @@ import { join } from 'node:path'; import { FlatMappingPaths, JsonTemplateEngine, PathType } from '../../src'; import { Scenario } from '../types'; +function getTemplate(scenarioDir: string, scenario: Scenario): string { + const templatePath = join(scenarioDir, Scenario.getTemplatePath(scenario)); + return readFileSync(templatePath, 'utf-8'); +} function initializeScenario(scenarioDir: string, scenario: Scenario) { scenario.options = scenario.options || {}; scenario.options.defaultPathType = scenario.options.defaultPathType || PathType.SIMPLE; - const templatePath = join(scenarioDir, Scenario.getTemplatePath(scenario)); - let template: string = readFileSync(templatePath, 'utf-8'); + let template = scenario.template ?? getTemplate(scenarioDir, scenario); if (scenario.containsMappings) { template = JsonTemplateEngine.convertMappingsToTemplate( JSON.parse(template) as FlatMappingPaths[], @@ -19,13 +22,22 @@ function initializeScenario(scenarioDir: string, scenario: Scenario) { ); } -export function createTemplateEngine(scenarioDir: string, scenario: Scenario): JsonTemplateEngine { +export function evaluateScenario(scenarioDir: string, scenario: Scenario): any { initializeScenario(scenarioDir, scenario); - return JsonTemplateEngine.create(scenario.template as string, scenario.options); -} - -export function evaluateScenario(templateEngine: JsonTemplateEngine, scenario: Scenario): any { - return templateEngine.evaluate(scenario.input, scenario.bindings); + if (scenario.containsMappings) { + return JsonTemplateEngine.evaluateAsSync( + scenario.template, + scenario.options, + scenario.input, + scenario.bindings, + ); + } + return JsonTemplateEngine.evaluate( + scenario.template, + scenario.options, + scenario.input, + scenario.bindings, + ); } export function extractScenarios(scenarioDir: string): Scenario[] {