ondc-code-generator 0.6.22 → 0.7.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/copilot-instructions.md +128 -0
- package/GOLANG_IMPLEMENTATION.md +156 -0
- package/README.md +10 -1
- package/dist/bin/cli-tool.d.ts +70 -0
- package/dist/bin/cli-tool.js +310 -0
- package/dist/bin/cli.d.ts +2 -0
- package/dist/bin/cli.js +80 -0
- package/dist/generator/config-compiler.d.ts +1 -0
- package/dist/generator/config-compiler.js +14 -0
- package/dist/generator/generators/go/go-ast.d.ts +1 -0
- package/dist/generator/generators/go/go-ast.js +60 -0
- package/dist/generator/generators/go/go-generator.d.ts +13 -0
- package/dist/generator/generators/go/go-generator.js +322 -0
- package/dist/generator/generators/go/templates/api-tests.mustache +65 -0
- package/dist/generator/generators/go/templates/go-mod.mustache +3 -0
- package/dist/generator/generators/go/templates/index.mustache +34 -0
- package/dist/generator/generators/go/templates/json-normalizer.mustache +155 -0
- package/dist/generator/generators/go/templates/json-path-utils.mustache +63 -0
- package/dist/generator/generators/go/templates/storage-templates/api-save-utils.mustache +84 -0
- package/dist/generator/generators/go/templates/storage-templates/api-save.mustache +44 -0
- package/dist/generator/generators/go/templates/storage-templates/index.mustache +72 -0
- package/dist/generator/generators/go/templates/storage-templates/save-utils.mustache +75 -0
- package/dist/generator/generators/go/templates/storage-templates/storage-interface.mustache +107 -0
- package/dist/generator/generators/go/templates/test-config.mustache +62 -0
- package/dist/generator/generators/go/templates/test-object.mustache +52 -0
- package/dist/generator/generators/go/templates/validation-code.mustache +66 -0
- package/dist/generator/generators/go/templates/validation-utils.mustache +321 -0
- package/dist/generator/generators/typescript/templates/index.mustache +1 -1
- package/dist/generator/generators/typescript/ts-generator.js +2 -2
- package/dist/index.d.ts +7 -1
- package/dist/index.js +6 -1
- package/dist/index.test.js +8 -1
- package/dist/types/build.d.ts +2 -0
- package/dist/types/compiler-types.d.ts +2 -1
- package/dist/types/compiler-types.js +1 -0
- package/dist/utils/fs-utils.d.ts +1 -0
- package/dist/utils/fs-utils.js +24 -0
- package/dist/utils/general-utils/string-utils.d.ts +1 -0
- package/dist/utils/general-utils/string-utils.js +11 -0
- package/package.json +5 -1
- package/sample.md +273 -0
- package/test-python-session.js +0 -0
package/dist/bin/cli.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import path from "path";
|
|
3
|
+
import fs from "fs/promises";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
5
|
+
import { program } from "commander";
|
|
6
|
+
import { ConfigCompiler, SupportedLanguages } from "../index.js";
|
|
7
|
+
import Cli from "./cli-tool.js";
|
|
8
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
// interface CLIOptions {
|
|
10
|
+
// config: string;
|
|
11
|
+
// output: string;
|
|
12
|
+
// lang: string;
|
|
13
|
+
// }
|
|
14
|
+
program
|
|
15
|
+
.name("ondc-code-generator")
|
|
16
|
+
.description("Ondc Validation Code Generator")
|
|
17
|
+
.version("0.0.1");
|
|
18
|
+
program
|
|
19
|
+
.command("ondc-validation-gen")
|
|
20
|
+
.alias("xval")
|
|
21
|
+
.option("-c, --config <path>", "Path to build.yaml file")
|
|
22
|
+
.option("-f, --function-name <name>", "Name of the validation function to generate")
|
|
23
|
+
.option("-o, --output <directory>", "Output directory for generated code")
|
|
24
|
+
.option("-l, --lang <language>", "Target programming language (typescript, python, javascript, go)")
|
|
25
|
+
.description("Generate validation code")
|
|
26
|
+
.action(async (options) => {
|
|
27
|
+
const { config, output, lang, functionName } = options;
|
|
28
|
+
console.log(Cli.title("Ondc Validation Code Generator"));
|
|
29
|
+
if (!config || !output || !lang) {
|
|
30
|
+
console.log(Cli.description.error("Please provide all required options: --config, --output, --lang"));
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
try {
|
|
34
|
+
console.log(Cli.description.info(`Generating validation code for language: ${lang}`));
|
|
35
|
+
const functionName = options.functionName || "L1validations";
|
|
36
|
+
const language = getSupportedLanguage(lang);
|
|
37
|
+
const compiler = new ConfigCompiler(language);
|
|
38
|
+
const buildPath = path.resolve(process.cwd(), config);
|
|
39
|
+
console.log(Cli.description.info(`Reading build file from ${buildPath}...`));
|
|
40
|
+
const buildYaml = await fs.readFile(buildPath, "utf-8");
|
|
41
|
+
console.log(Cli.description.info("Initializing compiler..."));
|
|
42
|
+
await compiler.initialize(buildYaml);
|
|
43
|
+
console.log(Cli.description.info("Generating validation code..."));
|
|
44
|
+
await compiler.generateValidationFromBuild(functionName, output);
|
|
45
|
+
console.log(Cli.description.success(`Validation code generated successfully in ${output} for language ${lang}`));
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
49
|
+
console.error(Cli.description.error(`Error: ${message}`));
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
program
|
|
54
|
+
.command("schema-gen")
|
|
55
|
+
.alias("schema")
|
|
56
|
+
.option("-c, --config <path>", "Path to build.yaml file")
|
|
57
|
+
.option("-o, --output <directory>", "Output directory for generated schema")
|
|
58
|
+
.option("-f, --format <format>", "Output format (json, yaml,typescript)")
|
|
59
|
+
.description("Generate L0 schema")
|
|
60
|
+
.action(async () => {
|
|
61
|
+
console.log("Schema generation command invoked");
|
|
62
|
+
});
|
|
63
|
+
program.parse();
|
|
64
|
+
function getSupportedLanguage(lang) {
|
|
65
|
+
switch (lang.toLowerCase()) {
|
|
66
|
+
case "typescript":
|
|
67
|
+
return SupportedLanguages.Typescript;
|
|
68
|
+
case "python":
|
|
69
|
+
return SupportedLanguages.Python;
|
|
70
|
+
case "javascript":
|
|
71
|
+
return SupportedLanguages.Javascript;
|
|
72
|
+
case "go":
|
|
73
|
+
return SupportedLanguages.Golang;
|
|
74
|
+
default:
|
|
75
|
+
throw new Error(`Unsupported language: ${lang}. Supported languages are: ${getValidLanguageOptions()}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function getValidLanguageOptions() {
|
|
79
|
+
return Object.values(SupportedLanguages).join(", ");
|
|
80
|
+
}
|
|
@@ -23,5 +23,6 @@ export declare class ConfigCompiler {
|
|
|
23
23
|
generateCode: (valConfig: ValidationConfig, codeName?: string, minimal?: boolean, outputPath?: string) => Promise<void>;
|
|
24
24
|
generateL0Schema: (outputPath?: string, type?: "json" | "typescript") => Promise<void>;
|
|
25
25
|
generateValidPaths: () => Promise<Record<string, string[]>>;
|
|
26
|
+
generateValidationFromBuild: (codeName: string, outputPath: string) => Promise<void>;
|
|
26
27
|
}
|
|
27
28
|
export {};
|
|
@@ -11,6 +11,7 @@ import path from "path";
|
|
|
11
11
|
import { duplicateVariablesInChildren } from "../utils/config-utils/duplicateVariables.js";
|
|
12
12
|
import { PythonGenerator } from "./generators/python/py-generator.js";
|
|
13
13
|
import { JavascriptGenerator } from "./generators/javascript/js-generator.js";
|
|
14
|
+
import { GoGenerator } from "./generators/go/go-generator.js";
|
|
14
15
|
const __filename = fileURLToPath(import.meta.url);
|
|
15
16
|
const __dirname = path.dirname(__filename);
|
|
16
17
|
const defaultConfig = {
|
|
@@ -85,6 +86,11 @@ export class ConfigCompiler {
|
|
|
85
86
|
codeName: codeName,
|
|
86
87
|
});
|
|
87
88
|
break;
|
|
89
|
+
case SupportedLanguages.Golang:
|
|
90
|
+
await new GoGenerator(valConfig, this.errorDefinitions ?? [], targetPath).generateCode({
|
|
91
|
+
codeName: codeName,
|
|
92
|
+
});
|
|
93
|
+
break;
|
|
88
94
|
default:
|
|
89
95
|
throw new Error("Language not supported");
|
|
90
96
|
}
|
|
@@ -123,6 +129,14 @@ export class ConfigCompiler {
|
|
|
123
129
|
// );
|
|
124
130
|
return this.possibleJsonPaths;
|
|
125
131
|
};
|
|
132
|
+
this.generateValidationFromBuild = async (codeName, outputPath) => {
|
|
133
|
+
if (!this.buildData)
|
|
134
|
+
throw new Error("Build data not initialized");
|
|
135
|
+
const valConfig = this.buildData["x-validations"];
|
|
136
|
+
if (!valConfig)
|
|
137
|
+
throw new Error("No validation config found in build data");
|
|
138
|
+
await this.generateCode(valConfig, codeName, false, outputPath);
|
|
139
|
+
};
|
|
126
140
|
this.language = language;
|
|
127
141
|
this.SchemaExtractionService = new SchemaExtractionService();
|
|
128
142
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const compileInputToGo: (input: string) => string;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { buildAstFromInput } from "../../../services/return-complier/combined.js";
|
|
2
|
+
import { AllIn, AnyIn, AreUnique, EqualTo, FollowRegex, GreaterThan, LessThan, NoneIn, ArePresent, } from "../../../services/return-complier/tokens.js";
|
|
3
|
+
const uniaryFunction = {
|
|
4
|
+
[AreUnique.LABEL ?? "are unique"]: "AreUnique",
|
|
5
|
+
[ArePresent.LABEL ?? "are present"]: "ArePresent",
|
|
6
|
+
};
|
|
7
|
+
const binaryFunction = {
|
|
8
|
+
[AllIn.LABEL ?? "all in"]: "AllIn",
|
|
9
|
+
[AnyIn.LABEL ?? "any in"]: "AnyIn",
|
|
10
|
+
[FollowRegex.LABEL ?? "follow regex"]: "FollowRegex",
|
|
11
|
+
[NoneIn.LABEL ?? "none in"]: "NoneIn",
|
|
12
|
+
[EqualTo.LABEL ?? "equal to"]: "EqualTo",
|
|
13
|
+
[GreaterThan.LABEL ?? "greater than"]: "GreaterThan",
|
|
14
|
+
[LessThan.LABEL ?? "less than"]: "LessThan",
|
|
15
|
+
};
|
|
16
|
+
function getGoOperator(op) {
|
|
17
|
+
switch (op) {
|
|
18
|
+
case "&&":
|
|
19
|
+
return "&&";
|
|
20
|
+
case "||":
|
|
21
|
+
return "||";
|
|
22
|
+
default:
|
|
23
|
+
return op;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function compileToGo(node) {
|
|
27
|
+
if (node.type === "returnStatement") {
|
|
28
|
+
const returnNode = node;
|
|
29
|
+
return compileToGo(returnNode.expression);
|
|
30
|
+
}
|
|
31
|
+
if (node.type === "binaryOperator") {
|
|
32
|
+
const binaryNode = node;
|
|
33
|
+
const lhs = compileToGo(binaryNode.lhs);
|
|
34
|
+
const rhs = compileToGo(binaryNode.rhs);
|
|
35
|
+
return `(${lhs}) ${getGoOperator(binaryNode.operator)} (${rhs})`;
|
|
36
|
+
}
|
|
37
|
+
if (node.type === "notOperator") {
|
|
38
|
+
const notNode = node;
|
|
39
|
+
const expression = compileToGo(notNode.expression);
|
|
40
|
+
return `!(${expression})`;
|
|
41
|
+
}
|
|
42
|
+
if (node.type === "customUniaryFunction") {
|
|
43
|
+
const unary = node;
|
|
44
|
+
const func = uniaryFunction[unary.customFunction];
|
|
45
|
+
const varName = unary.expression.name;
|
|
46
|
+
return `validationutils.${func}(${varName})`;
|
|
47
|
+
}
|
|
48
|
+
if (node.type === "customBinaryFunction") {
|
|
49
|
+
const binary = node;
|
|
50
|
+
const func = binaryFunction[binary.customFunction];
|
|
51
|
+
const lhs = binary.lhs.name;
|
|
52
|
+
const rhs = binary.rhs.name;
|
|
53
|
+
return `validationutils.${func}(${lhs}, ${rhs})`;
|
|
54
|
+
}
|
|
55
|
+
throw new Error("Unknown node type");
|
|
56
|
+
}
|
|
57
|
+
export const compileInputToGo = (input) => {
|
|
58
|
+
const ast = buildAstFromInput(input);
|
|
59
|
+
return compileToGo(ast);
|
|
60
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { CodeGenerator, CodeGeneratorProps } from "../classes/abstract-generator.js";
|
|
2
|
+
export declare class GoGenerator extends CodeGenerator {
|
|
3
|
+
codeConfig: CodeGeneratorProps | undefined;
|
|
4
|
+
generateSessionDataCode(): Promise<void>;
|
|
5
|
+
generateValidationCode(): Promise<void>;
|
|
6
|
+
generateCode: (codeConfig: CodeGeneratorProps) => Promise<void>;
|
|
7
|
+
private generateIndexFile;
|
|
8
|
+
private getExternalKeys;
|
|
9
|
+
private generateTestFunction;
|
|
10
|
+
private createVariablesCode;
|
|
11
|
+
private createValidationLogicCode;
|
|
12
|
+
private CreateErrorMarkdown;
|
|
13
|
+
}
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import { readFileSync } from "fs";
|
|
2
|
+
import { CodeGenerator, } from "../classes/abstract-generator.js";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
5
|
+
import Mustache from "mustache";
|
|
6
|
+
import { ConfigSyntax, ExternalDataSyntax, TestObjectSyntax, } from "../../../constants/syntax.js";
|
|
7
|
+
import { writeAndFormatCode } from "../../../utils/fs-utils.js";
|
|
8
|
+
import { collectLoadData } from "../../../utils/config-utils/load-variables.js";
|
|
9
|
+
import { compileInputToGo } from "./go-ast.js";
|
|
10
|
+
import { getVariablesFromTest } from "../../../utils/general-utils/test-object-utils.js";
|
|
11
|
+
import { ConvertArrayToStringGoStyle } from "../../../utils/general-utils/string-utils.js";
|
|
12
|
+
import { markdownMessageGenerator } from "../documentation/markdown-message-generator.js";
|
|
13
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
14
|
+
const __dirname = path.dirname(__filename);
|
|
15
|
+
const packageName = "validationpkg";
|
|
16
|
+
export class GoGenerator extends CodeGenerator {
|
|
17
|
+
constructor() {
|
|
18
|
+
super(...arguments);
|
|
19
|
+
this.generateCode = async (codeConfig) => {
|
|
20
|
+
this.codeConfig = codeConfig;
|
|
21
|
+
const jsonPathUtilsCode = readFileSync(path.resolve(__dirname, "./templates/json-path-utils.mustache"), "utf-8");
|
|
22
|
+
const validationUtils = readFileSync(path.resolve(__dirname, "./templates/validation-utils.mustache"), "utf-8");
|
|
23
|
+
const typesTemplate = readFileSync(path.resolve(__dirname, "./templates/test-config.mustache"), "utf-8");
|
|
24
|
+
const normalizerTemplate = readFileSync(path.resolve(__dirname, "./templates/json-normalizer.mustache"), "utf-8");
|
|
25
|
+
const goMod = readFileSync(path.resolve(__dirname, "./templates/go-mod.mustache"), "utf-8");
|
|
26
|
+
const typesCode = Mustache.render(typesTemplate, {
|
|
27
|
+
externalData: this.getExternalKeys(),
|
|
28
|
+
});
|
|
29
|
+
writeAndFormatCode(this.rootPath, `./${packageName}/validationutils/json_path_utils.go`, jsonPathUtilsCode, "go");
|
|
30
|
+
writeAndFormatCode(this.rootPath, `./${packageName}/validationutils/validation_utils.go`, validationUtils, "go");
|
|
31
|
+
writeAndFormatCode(this.rootPath, `./${packageName}/validationutils/test-config.go`, typesCode, "go");
|
|
32
|
+
writeAndFormatCode(this.rootPath, `./${packageName}/validationutils/json_normalizer.go`, normalizerTemplate, "go");
|
|
33
|
+
await this.generateValidationCode();
|
|
34
|
+
await writeAndFormatCode(this.rootPath, `./${packageName}/main-validator.go`, this.generateIndexFile(Object.keys(this.validationConfig[ConfigSyntax.Tests]), codeConfig.codeName), "go");
|
|
35
|
+
await writeAndFormatCode(this.rootPath, `./${packageName}/go.mod`, goMod, "text");
|
|
36
|
+
await this.generateSessionDataCode();
|
|
37
|
+
};
|
|
38
|
+
this.generateTestFunction = async (testObject) => {
|
|
39
|
+
const template = readFileSync(path.resolve(__dirname, "./templates/test-object.mustache"), "utf-8");
|
|
40
|
+
const view = {
|
|
41
|
+
name: stringToCaps(testObject[TestObjectSyntax.Name]),
|
|
42
|
+
scopePath: testObject[TestObjectSyntax.Scope] ?? "$",
|
|
43
|
+
variables: this.createVariablesCode(testObject),
|
|
44
|
+
hasContinue: testObject[TestObjectSyntax.Continue] ? true : false,
|
|
45
|
+
skipCheckStatement: testObject[TestObjectSyntax.Continue]
|
|
46
|
+
? compileInputToGo(testObject[TestObjectSyntax.Continue])
|
|
47
|
+
: undefined,
|
|
48
|
+
validationCode: await this.createValidationLogicCode(testObject),
|
|
49
|
+
successCode: testObject[TestObjectSyntax.SuccessCode] ?? 200,
|
|
50
|
+
errorCode: testObject[TestObjectSyntax.ErrorCode] ?? 30000,
|
|
51
|
+
testName: testObject[TestObjectSyntax.Name],
|
|
52
|
+
TEST_OBJECT: `${JSON.stringify(testObject)}`,
|
|
53
|
+
};
|
|
54
|
+
return {
|
|
55
|
+
funcName: testObject[TestObjectSyntax.Name],
|
|
56
|
+
code: Mustache.render(template, view),
|
|
57
|
+
};
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
async generateSessionDataCode() {
|
|
61
|
+
if (!this.codeConfig) {
|
|
62
|
+
throw new Error("Code config is not set");
|
|
63
|
+
}
|
|
64
|
+
const sessionData = this.validationConfig[ConfigSyntax.SessionData];
|
|
65
|
+
const tests = this.validationConfig[ConfigSyntax.Tests];
|
|
66
|
+
const relevantSessionData = {};
|
|
67
|
+
collectLoadData(tests, relevantSessionData);
|
|
68
|
+
const sessionDataUtilsTemplate = readFileSync(path.resolve(__dirname, "./templates/storage-templates/save-utils.mustache"), "utf-8");
|
|
69
|
+
const storageInterfaceTemplate = readFileSync(path.resolve(__dirname, "./templates/storage-templates/storage-interface.mustache"), "utf-8");
|
|
70
|
+
const indexTemplate = readFileSync(path.resolve(__dirname, "./templates/storage-templates/index.mustache"), "utf-8");
|
|
71
|
+
const saveActionTemplate = readFileSync(path.resolve(__dirname, "./templates/storage-templates/api-save.mustache"), "utf-8");
|
|
72
|
+
const saveActionUtilsTemplate = readFileSync(path.resolve(__dirname, "./templates/storage-templates/api-save-utils.mustache"), "utf-8");
|
|
73
|
+
const allActions = Object.keys(tests);
|
|
74
|
+
const indexCode = Mustache.render(indexTemplate, {
|
|
75
|
+
actions: Array.from(allActions).map((action) => {
|
|
76
|
+
return { action: action };
|
|
77
|
+
}),
|
|
78
|
+
functionName: this.codeConfig.codeName.replace(/[^a-zA-Z0-9_]/g, ""),
|
|
79
|
+
});
|
|
80
|
+
for (const action of allActions) {
|
|
81
|
+
const loadData = relevantSessionData[action] || {};
|
|
82
|
+
const saveData = sessionData[action] || {};
|
|
83
|
+
const saveCode = Mustache.render(saveActionTemplate, {
|
|
84
|
+
storeActions: Object.keys(saveData).map((key) => {
|
|
85
|
+
return {
|
|
86
|
+
key: key,
|
|
87
|
+
value: saveData[key],
|
|
88
|
+
};
|
|
89
|
+
}),
|
|
90
|
+
loadActions: Object.keys(loadData).map((key) => {
|
|
91
|
+
console.log(loadData[key]);
|
|
92
|
+
return {
|
|
93
|
+
key: loadData[key],
|
|
94
|
+
};
|
|
95
|
+
}),
|
|
96
|
+
action: action,
|
|
97
|
+
});
|
|
98
|
+
await writeAndFormatCode(this.rootPath, `./${packageName}/storageutils/${action}.go`, saveCode, "go");
|
|
99
|
+
}
|
|
100
|
+
await writeAndFormatCode(this.rootPath, `./${packageName}/storageutils/save_utils.go`, sessionDataUtilsTemplate, "go");
|
|
101
|
+
await writeAndFormatCode(this.rootPath, `./${packageName}/validationutils/storage-interface.go`, storageInterfaceTemplate, "go");
|
|
102
|
+
await writeAndFormatCode(this.rootPath, `./${packageName}/storageutils/index.go`, indexCode, "go");
|
|
103
|
+
await writeAndFormatCode(this.rootPath, `./${packageName}/storageutils/api_save_utils.go`, saveActionUtilsTemplate, "go");
|
|
104
|
+
}
|
|
105
|
+
async generateValidationCode() {
|
|
106
|
+
const testConfig = this.validationConfig[ConfigSyntax.Tests];
|
|
107
|
+
for (const key in testConfig) {
|
|
108
|
+
const testObjects = testConfig[key];
|
|
109
|
+
const betaConfig = {
|
|
110
|
+
[TestObjectSyntax.Name]: key + "Validations",
|
|
111
|
+
[TestObjectSyntax.Return]: testObjects,
|
|
112
|
+
};
|
|
113
|
+
const testFunction = await this.generateTestFunction(betaConfig);
|
|
114
|
+
const apiTestTemplate = readFileSync(path.resolve(__dirname, "./templates/api-tests.mustache"), "utf-8");
|
|
115
|
+
const finalCode = Mustache.render(apiTestTemplate, {
|
|
116
|
+
functionCode: testFunction.code,
|
|
117
|
+
apiName: stringToCaps(key),
|
|
118
|
+
});
|
|
119
|
+
await writeAndFormatCode(this.rootPath, `./${packageName}/jsonvalidations/${key}.go`, finalCode, "go");
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
generateIndexFile(apis, functionName = "L1Validations") {
|
|
123
|
+
functionName = functionName.replace(/[^a-zA-Z0-9_]/g, "");
|
|
124
|
+
let importList = [
|
|
125
|
+
`"validationpkg/validationutils"`,
|
|
126
|
+
`"validationpkg/jsonvalidations"`,
|
|
127
|
+
`"fmt"`,
|
|
128
|
+
`"encoding/json"`,
|
|
129
|
+
`"validationpkg/storageutils"`,
|
|
130
|
+
];
|
|
131
|
+
const masterTemplate = readFileSync(path.resolve(__dirname, "./templates/index.mustache"), "utf-8");
|
|
132
|
+
const masterFunction = `func Perform${functionName}(
|
|
133
|
+
action string,
|
|
134
|
+
payload interface{},
|
|
135
|
+
config *validationutils.ValidationConfig,
|
|
136
|
+
externalData validationutils.ExternalData,
|
|
137
|
+
) ([]validationutils.ValidationOutput, error) {
|
|
138
|
+
completeConfig := getCompleteConfig(config)
|
|
139
|
+
|
|
140
|
+
// Validate stateful requirements
|
|
141
|
+
if completeConfig.StateFullValidations {
|
|
142
|
+
if completeConfig.Store == nil {
|
|
143
|
+
return nil, fmt.Errorf("stateful validations require a storage interface to be provided in the config")
|
|
144
|
+
}
|
|
145
|
+
if completeConfig.UniqueKey == nil || *completeConfig.UniqueKey == "" {
|
|
146
|
+
return nil, fmt.Errorf("stateful validations require a uniqueKey to be provided in the config")
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
normalizedPayload := validationutils.NormalizeKeys(payload)
|
|
152
|
+
|
|
153
|
+
// Set _SELF
|
|
154
|
+
externalData.Self = normalizedPayload
|
|
155
|
+
|
|
156
|
+
// Load stateful data if needed
|
|
157
|
+
if completeConfig.StateFullValidations {
|
|
158
|
+
loadedData, err := Perform${functionName}Load(action, *completeConfig.UniqueKey, completeConfig.Store)
|
|
159
|
+
if err != nil {
|
|
160
|
+
return nil, fmt.Errorf("failed to load stateful data: %w", err)
|
|
161
|
+
}
|
|
162
|
+
// Merge loaded data with external data
|
|
163
|
+
externalData = mergeExternalData(loadedData, externalData)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Create validation input
|
|
167
|
+
input := validationutils.ValidationInput {
|
|
168
|
+
Payload: normalizedPayload,
|
|
169
|
+
ExternalData: externalData,
|
|
170
|
+
Config: completeConfig,
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Route to action-specific validation
|
|
174
|
+
switch action {
|
|
175
|
+
${apis
|
|
176
|
+
.map((api) => `
|
|
177
|
+
case "${api}":
|
|
178
|
+
return jsonvalidations.${stringToCaps(api)}_Tests(input)
|
|
179
|
+
`)
|
|
180
|
+
.join("\n")}
|
|
181
|
+
default:
|
|
182
|
+
return nil, fmt.Errorf("action not found: %s", action)
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// getCompleteConfig returns a complete config with defaults
|
|
187
|
+
func getCompleteConfig(config *validationutils.ValidationConfig) validationutils.ValidationConfig {
|
|
188
|
+
if config == nil {
|
|
189
|
+
return validationutils.ValidationConfig{
|
|
190
|
+
OnlyInvalid: true,
|
|
191
|
+
HideParentErrors: true,
|
|
192
|
+
StateFullValidations: false,
|
|
193
|
+
Debug: false,
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Return copy with defaults for unset fields
|
|
198
|
+
completeConfig := *config
|
|
199
|
+
// Go doesn't have a clean way to check if bool was explicitly set,
|
|
200
|
+
// so we assume false means "use default true" only if it seems intentional
|
|
201
|
+
// In practice, you might want to use pointers for optional bools
|
|
202
|
+
return completeConfig
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// mergeExternalData merges loaded data with provided external data
|
|
206
|
+
// using JSON marshal/unmarshal for a generic merge strategy.
|
|
207
|
+
// Non-null fields in provided override loaded fields.
|
|
208
|
+
func mergeExternalData(loaded, provided validationutils.ExternalData) validationutils.ExternalData {
|
|
209
|
+
// Convert to maps
|
|
210
|
+
loadedBytes, _ := json.Marshal(loaded)
|
|
211
|
+
providedBytes, _ := json.Marshal(provided)
|
|
212
|
+
|
|
213
|
+
var loadedMap, providedMap map[string]interface{}
|
|
214
|
+
json.Unmarshal(loadedBytes, &loadedMap)
|
|
215
|
+
json.Unmarshal(providedBytes, &providedMap)
|
|
216
|
+
|
|
217
|
+
// Merge provided into loaded
|
|
218
|
+
for key, value := range providedMap {
|
|
219
|
+
if value != nil {
|
|
220
|
+
loadedMap[key] = value
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Convert back
|
|
225
|
+
var result validationutils.ExternalData
|
|
226
|
+
mergedBytes, _ := json.Marshal(loadedMap)
|
|
227
|
+
json.Unmarshal(mergedBytes, &result)
|
|
228
|
+
|
|
229
|
+
return result
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
var Perform${functionName}Load = storageutils.Perform${functionName}Load
|
|
233
|
+
var Perform${functionName}Save = storageutils.Perform${functionName}Save
|
|
234
|
+
`;
|
|
235
|
+
const importCode = `import (
|
|
236
|
+
${importList.map((imp) => `\t${imp}`).join("\n")}
|
|
237
|
+
)`;
|
|
238
|
+
return Mustache.render(masterTemplate, {
|
|
239
|
+
importCode: importCode,
|
|
240
|
+
masterFunction: masterFunction,
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
getExternalKeys() {
|
|
244
|
+
const apis = Object.keys(this.validationConfig[ConfigSyntax.SessionData]);
|
|
245
|
+
let result = [];
|
|
246
|
+
for (const api of apis) {
|
|
247
|
+
const keys = Object.keys(this.validationConfig[ConfigSyntax.SessionData][api]);
|
|
248
|
+
for (const key of keys) {
|
|
249
|
+
result.push({ name: key });
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
result = result.filter((v) => v.name !== "_SELF");
|
|
253
|
+
return result;
|
|
254
|
+
}
|
|
255
|
+
createVariablesCode(testObject) {
|
|
256
|
+
const variables = [];
|
|
257
|
+
const varNames = getVariablesFromTest(testObject);
|
|
258
|
+
for (const name of varNames) {
|
|
259
|
+
const value = testObject[name];
|
|
260
|
+
const final = typeof value === "string"
|
|
261
|
+
? `validationutils.GetJsonPath(testObjMap, "${value}",true)`
|
|
262
|
+
: ConvertArrayToStringGoStyle(value);
|
|
263
|
+
variables.push({
|
|
264
|
+
name: name,
|
|
265
|
+
value: final,
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
return variables;
|
|
269
|
+
}
|
|
270
|
+
async createValidationLogicCode(testObject) {
|
|
271
|
+
const template = readFileSync(path.resolve(__dirname, "./templates/validation-code.mustache"), "utf-8");
|
|
272
|
+
const skip = testObject[TestObjectSyntax.Continue];
|
|
273
|
+
const skipList = skip ? [skip] : undefined;
|
|
274
|
+
if (typeof testObject[TestObjectSyntax.Return] === "string") {
|
|
275
|
+
const returnStatement = compileInputToGo(testObject[TestObjectSyntax.Return]);
|
|
276
|
+
let isStateFull = false;
|
|
277
|
+
for (const k in testObject) {
|
|
278
|
+
const value = testObject[k];
|
|
279
|
+
if (typeof value === "string") {
|
|
280
|
+
if (value.includes(`${ExternalDataSyntax}.`) &&
|
|
281
|
+
!value.includes("_SELF")) {
|
|
282
|
+
isStateFull = true;
|
|
283
|
+
break;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
return Mustache.render(template, {
|
|
288
|
+
isNested: false,
|
|
289
|
+
isStateFull: isStateFull,
|
|
290
|
+
returnStatement: returnStatement,
|
|
291
|
+
errorCode: testObject[TestObjectSyntax.ErrorCode] ?? 30000,
|
|
292
|
+
errorDescription: this.CreateErrorMarkdown(testObject, skipList),
|
|
293
|
+
testName: testObject[TestObjectSyntax.Name],
|
|
294
|
+
TEST_OBJECT: `${JSON.stringify(testObject)}`,
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
else {
|
|
298
|
+
const subObjects = testObject[TestObjectSyntax.Return];
|
|
299
|
+
const functionCodes = [];
|
|
300
|
+
for (const subObject of subObjects) {
|
|
301
|
+
const func = await this.generateTestFunction(subObject);
|
|
302
|
+
functionCodes.push(func);
|
|
303
|
+
}
|
|
304
|
+
const names = functionCodes.map((f) => {
|
|
305
|
+
return { name: f.funcName };
|
|
306
|
+
});
|
|
307
|
+
return Mustache.render(template, {
|
|
308
|
+
isNested: true,
|
|
309
|
+
nestedFunctions: functionCodes.map((f) => f.code).join("\n"),
|
|
310
|
+
names: names,
|
|
311
|
+
testName: testObject[TestObjectSyntax.Name],
|
|
312
|
+
TEST_OBJECT: `${JSON.stringify(testObject)}`,
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
CreateErrorMarkdown(testObject, skipList) {
|
|
317
|
+
return markdownMessageGenerator(testObject[TestObjectSyntax.Return], testObject, testObject[TestObjectSyntax.Name], skipList);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
function stringToCaps(str) {
|
|
321
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
322
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// Code generated by github.com/ONDC-Official/automation-validation-compiler, DO NOT EDIT.
|
|
2
|
+
|
|
3
|
+
package jsonvalidations
|
|
4
|
+
|
|
5
|
+
import (
|
|
6
|
+
"validationpkg/validationutils"
|
|
7
|
+
"fmt"
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
func {{apiName}}_Tests(input validationutils.ValidationInput) ([]validationutils.ValidationOutput, error) {
|
|
11
|
+
totalResults, err := {{apiName}}Validations(input)
|
|
12
|
+
if err != nil {
|
|
13
|
+
return nil, err
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if !input.Config.Debug {
|
|
17
|
+
for i := range totalResults {
|
|
18
|
+
totalResults[i].DebugInfo = nil
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if input.Config.HideParentErrors {
|
|
23
|
+
// Delete results with valid false and no description
|
|
24
|
+
filtered := make([]validationutils.ValidationOutput, 0)
|
|
25
|
+
for _, r := range totalResults {
|
|
26
|
+
if !r.Valid && r.Description == "" {
|
|
27
|
+
continue
|
|
28
|
+
}
|
|
29
|
+
filtered = append(filtered, r)
|
|
30
|
+
}
|
|
31
|
+
totalResults = filtered
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if input.Config.OnlyInvalid {
|
|
35
|
+
res := make([]validationutils.ValidationOutput, 0)
|
|
36
|
+
for _, r := range totalResults {
|
|
37
|
+
if !r.Valid {
|
|
38
|
+
res = append(res, r)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if len(res) == 0 {
|
|
43
|
+
// Find the overall test result
|
|
44
|
+
var targetSuccess *validationutils.ValidationOutput
|
|
45
|
+
for i := range totalResults {
|
|
46
|
+
if totalResults[i].TestName == "{{apiName}}Validations" {
|
|
47
|
+
targetSuccess = &totalResults[i]
|
|
48
|
+
break
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if targetSuccess == nil {
|
|
53
|
+
panic("Critical: Overall test result not found")
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return []validationutils.ValidationOutput{*targetSuccess}, nil
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return res, nil
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return totalResults, nil
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
{{{functionCode}}}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// Code generated by github.com/ONDC-Official/automation-validation-compiler, DO NOT EDIT.
|
|
2
|
+
package main
|
|
3
|
+
|
|
4
|
+
{{{importCode}}}
|
|
5
|
+
|
|
6
|
+
// PerformL1Validations performs Level-1 validations against a payload for a given action.
|
|
7
|
+
//
|
|
8
|
+
// Output shape - ValidationOutput is a slice of:
|
|
9
|
+
// - TestName: string
|
|
10
|
+
// - Valid: boolean
|
|
11
|
+
// - Code: number
|
|
12
|
+
// - Description: *string (optional)
|
|
13
|
+
// - DebugInfo: *DebugInfo (optional) with FedConfig
|
|
14
|
+
//
|
|
15
|
+
// Config - ValidationConfig (all fields optional):
|
|
16
|
+
// - OnlyInvalid (default true)
|
|
17
|
+
// - HideParentErrors (default true)
|
|
18
|
+
// - Debug (default false)
|
|
19
|
+
// - StateFullValidations (default false)
|
|
20
|
+
// - UniqueKey (optional)
|
|
21
|
+
// - Store (optional)
|
|
22
|
+
//
|
|
23
|
+
// Parameters:
|
|
24
|
+
// - action: The action name to validate against
|
|
25
|
+
// - payload: The JSON payload to validate (map[string]interface{} or struct)
|
|
26
|
+
// - config: Partial ValidationConfig. Merged with defaults
|
|
27
|
+
// - externalData: Extra data accessible to rules (Self will be set to normalized payload)
|
|
28
|
+
//
|
|
29
|
+
// Returns: []ValidationOutput
|
|
30
|
+
//
|
|
31
|
+
// Example:
|
|
32
|
+
// results := PerformL1Validations("search", payload, ValidationConfig{OnlyInvalid: false}, ExternalData{})
|
|
33
|
+
// // results[0] => ValidationOutput{ TestName, Valid, Code, Description, DebugInfo }
|
|
34
|
+
{{{masterFunction}}}
|