diff --git a/package-lock.json b/package-lock.json index 3ca643b..66d7518 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "@accordproject/concerto-util": "3.25.7", "@accordproject/markdown-common": "0.17.2", "@accordproject/markdown-template": "0.17.2", + "@accordproject/template-engine": "^2.8.0", "@typescript/twoslash": "^3.2.9", "browser-or-node": "^3.0.0", "dayjs": "1.11.13", @@ -735,6 +736,35 @@ "npm": ">=9" } }, + "node_modules/@accordproject/template-engine": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@accordproject/template-engine/-/template-engine-2.8.0.tgz", + "integrity": "sha512-ER7j3xqtH5vBNtkXkUgGCvSXr5W2M5eDu3hrLpDjHaVi4d77XXlNsUix5FMpqYaHz8XXNYR5KibrHDJp7y+vOg==", + "license": "Apache-2.0", + "dependencies": { + "@accordproject/cicero-core": "0.25.2", + "@accordproject/concerto-codegen": "3.32.1", + "@accordproject/concerto-core": "3.25.7", + "@accordproject/concerto-util": "3.25.7", + "@accordproject/markdown-common": "0.17.2", + "@accordproject/markdown-template": "0.17.2", + "@typescript/twoslash": "^3.2.9", + "browser-or-node": "^3.0.0", + "dayjs": "1.11.13", + "jsonpath": "^1.1.1", + "tar": "^7.4.3", + "to-words": "^4.4.0", + "traverse": "^0.6.11", + "typescript": "^5.8.2" + }, + "engines": { + "node": ">=20", + "npm": ">=6" + }, + "optionalDependencies": { + "@rollup/rollup-linux-x64-gnu": "^4.38.0" + } + }, "node_modules/@ampproject/remapping": { "version": "2.3.0", "dev": true, @@ -791,7 +821,6 @@ "version": "7.26.10", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.26.2", @@ -1333,7 +1362,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=18" }, @@ -1356,7 +1384,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=18" } @@ -3008,7 +3035,6 @@ "node_modules/@types/node": { "version": "22.13.14", "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~6.20.0" } @@ -3088,7 +3114,6 @@ "version": "8.28.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.28.0", "@typescript-eslint/types": "8.28.0", @@ -3306,7 +3331,6 @@ "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -3654,7 +3678,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001688", "electron-to-chromium": "^1.5.73", @@ -4422,7 +4445,6 @@ "dev": true, "hasInstallScript": true, "license": "MIT", - "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -4501,7 +4523,6 @@ "integrity": "sha512-VmQ+sifHUbI/IcSopBCF/HO3YiHQx/AVd3UVyYL6weuwW+HvON9VYn5l6Zl1WZzPWXPNZrSQpxwkkZ/VuvJZzg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -5954,7 +5975,6 @@ "version": "29.7.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/core": "^29.7.0", "@jest/types": "^29.6.3", @@ -8015,7 +8035,6 @@ "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/estree": "1.0.8" }, @@ -8874,7 +8893,6 @@ "version": "10.9.2", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -9049,7 +9067,6 @@ "node_modules/typescript": { "version": "5.8.2", "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/package.json b/package.json index d26f959..7cf05d8 100644 --- a/package.json +++ b/package.json @@ -42,9 +42,10 @@ "@accordproject/cicero-core": "0.25.2", "@accordproject/concerto-codegen": "3.32.1", "@accordproject/concerto-core": "3.25.7", - "@accordproject/concerto-util":"3.25.7", + "@accordproject/concerto-util": "3.25.7", "@accordproject/markdown-common": "0.17.2", "@accordproject/markdown-template": "0.17.2", + "@accordproject/template-engine": "^2.8.0", "@typescript/twoslash": "^3.2.9", "browser-or-node": "^3.0.0", "dayjs": "1.11.13", diff --git a/src/TemplateValidator.ts b/src/TemplateValidator.ts new file mode 100644 index 0000000..3280a63 --- /dev/null +++ b/src/TemplateValidator.ts @@ -0,0 +1,42 @@ +export interface ValidationResult { + errors: string[]; + warnings: string[]; +} + +export class TemplateValidator { + + static validate(template: string, model: Record): ValidationResult { + + const errors: string[] = []; + const warnings: string[] = []; + + // find variables like {{variable}} + const variableRegex = /{{\s*([a-zA-Z0-9_]+)\s*}}/g; + + const foundVariables = new Set(); + let match; + + while ((match = variableRegex.exec(template)) !== null) { + foundVariables.add(match[1]); + } + + // check if variables exist in model + foundVariables.forEach(variable => { + if (!(variable in model)) { + errors.push(`Variable '${variable}' not defined in TemplateData model`); + } + }); + + // optional: detect unused model fields + Object.keys(model).forEach(field => { + if (!foundVariables.has(field)) { + warnings.push(`Field '${field}' defined but not used in template`); + } + }); + + return { + errors, + warnings + }; + } +} \ No newline at end of file diff --git a/test/TemplateValidator.test.ts b/test/TemplateValidator.test.ts new file mode 100644 index 0000000..e02f882 --- /dev/null +++ b/test/TemplateValidator.test.ts @@ -0,0 +1,20 @@ +import { TemplateValidator } from '../src/TemplateValidator'; + +describe('TemplateValidator', () => { + + it('should detect missing variables', () => { + + const template = "Hello {{name}} {{age}}"; + + const model = { + name: "Alice" + }; + + const result = TemplateValidator.validate(template, model); + + expect(result.errors.length).toBe(1); + expect(result.errors[0]).toContain("age"); + + }); + +}); \ No newline at end of file