ondc-code-generator 0.5.51 → 0.6.0
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/dist/generator/generators/python/py-generator.d.ts +2 -1
- package/dist/generator/generators/python/py-generator.js +83 -3
- package/dist/generator/generators/python/templates/requirements.mustache +1 -0
- package/dist/generator/generators/python/templates/storage-templates/api-save.mustache +41 -0
- package/dist/generator/generators/python/templates/storage-templates/index.mustache +40 -0
- package/dist/generator/generators/python/templates/storage-templates/save-utils.mustache +22 -0
- package/dist/generator/generators/python/templates/storage-templates/storage-interface.mustache +93 -0
- package/dist/generator/generators/python/templates/storage-templates/storage-types.mustache +5 -0
- package/dist/generator/generators/python/templates/test-config.mustache +6 -0
- package/dist/generator/generators/python/templates/validation-code.mustache +9 -1
- package/dist/generator/generators/typescript/templates/storage-templates/api-save.mustache +58 -0
- package/dist/generator/generators/typescript/templates/storage-templates/index.mustache +37 -0
- package/dist/generator/generators/typescript/templates/storage-templates/save-utils.mustache +39 -0
- package/dist/generator/generators/typescript/templates/storage-templates/storage-interface.mustache +121 -0
- package/dist/generator/generators/typescript/templates/storage-templates/storage-types.mustache +6 -0
- package/dist/generator/generators/typescript/templates/test-config.mustache +16 -9
- package/dist/generator/generators/typescript/templates/test-object.mustache +1 -1
- package/dist/generator/generators/typescript/templates/validation-code.mustache +11 -1
- package/dist/generator/generators/typescript/ts-generator.d.ts +2 -1
- package/dist/generator/generators/typescript/ts-generator.js +94 -5
- package/dist/generator/validators/config-validator.js +11 -1
- package/dist/generator/validators/session-data-config/session-data-validator.d.ts +1 -0
- package/dist/generator/validators/session-data-config/session-data-validator.js +10 -0
- package/dist/index.js +0 -44
- package/dist/test.d.ts +1 -0
- package/dist/test.js +32 -0
- package/dist/utils/config-utils/load-variables.d.ts +3 -0
- package/dist/utils/config-utils/load-variables.js +33 -0
- package/package.json +2 -1
- package/dist/example.d.ts +0 -0
- package/dist/example.js +0 -22
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { TestObject } from "../../../types/config-types.js";
|
|
2
2
|
import { CodeGenerator, CodeGeneratorProps } from "../classes/abstract-generator.js";
|
|
3
3
|
export declare class PythonGenerator extends CodeGenerator {
|
|
4
|
-
|
|
4
|
+
codeConfig: CodeGeneratorProps | undefined;
|
|
5
|
+
generateSessionDataCode: () => Promise<void>;
|
|
5
6
|
generateValidationCode: () => Promise<void>;
|
|
6
7
|
generateCode: (codeConfig: CodeGeneratorProps) => Promise<void>;
|
|
7
8
|
generateTestFunction: (testObject: TestObject) => Promise<{
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { readFileSync } from "fs";
|
|
2
2
|
import path from "path";
|
|
3
3
|
import { fileURLToPath } from "url";
|
|
4
|
-
import { ConfigSyntax, TestObjectSyntax } from "../../../constants/syntax.js";
|
|
4
|
+
import { ConfigSyntax, TestObjectSyntax, ExternalDataSyntax, } from "../../../constants/syntax.js";
|
|
5
5
|
import Mustache from "mustache";
|
|
6
6
|
import { markdownMessageGenerator } from "../documentation/markdown-message-generator.js";
|
|
7
7
|
import { getVariablesFromTest as extractVariablesFromText } from "../../../utils/general-utils/test-object-utils.js";
|
|
@@ -9,13 +9,60 @@ import { compileInputToPy } from "./py-ast.js";
|
|
|
9
9
|
import { CodeGenerator, } from "../classes/abstract-generator.js";
|
|
10
10
|
import { writeAndFormatCode } from "../../../utils/fs-utils.js";
|
|
11
11
|
import { MarkdownDocGenerator } from "../documentation/md-generator.js";
|
|
12
|
+
import { collectLoadData } from "../../../utils/config-utils/load-variables.js";
|
|
12
13
|
const __filename = fileURLToPath(import.meta.url);
|
|
13
14
|
const __dirname = path.dirname(__filename);
|
|
14
15
|
export class PythonGenerator extends CodeGenerator {
|
|
15
16
|
constructor() {
|
|
16
17
|
super(...arguments);
|
|
17
18
|
this.generateSessionDataCode = async () => {
|
|
18
|
-
|
|
19
|
+
if (!this.codeConfig) {
|
|
20
|
+
throw new Error("Code config not set. Please call generateCode first.");
|
|
21
|
+
}
|
|
22
|
+
const sessionData = this.validationConfig[ConfigSyntax.SessionData];
|
|
23
|
+
const tests = this.validationConfig[ConfigSyntax.Tests];
|
|
24
|
+
const relevantSessionData = {};
|
|
25
|
+
collectLoadData(tests, relevantSessionData);
|
|
26
|
+
console.log("Relevant Session Data for Loading:", relevantSessionData);
|
|
27
|
+
const sessionDataUtilsTemplate = readFileSync(path.resolve(__dirname, "./templates/storage-templates/save-utils.mustache"), "utf-8");
|
|
28
|
+
const storageInterfaceTemplate = readFileSync(path.resolve(__dirname, "./templates/storage-templates/storage-interface.mustache"), "utf-8");
|
|
29
|
+
const storageTypesTemplate = readFileSync(path.resolve(__dirname, "./templates/storage-templates/storage-types.mustache"), "utf-8");
|
|
30
|
+
const indexTemplate = readFileSync(path.resolve(__dirname, "./templates/storage-templates/index.mustache"), "utf-8");
|
|
31
|
+
const saveActionTemplate = readFileSync(path.resolve(__dirname, "./templates/storage-templates/api-save.mustache"), "utf-8");
|
|
32
|
+
const allActions = Object.keys(tests);
|
|
33
|
+
const indexCode = Mustache.render(indexTemplate, {
|
|
34
|
+
actions: Array.from(allActions).map((action) => {
|
|
35
|
+
return { action: action };
|
|
36
|
+
}),
|
|
37
|
+
functionName: this.codeConfig.codeName.replace(/[^a-zA-Z0-9_]/g, ""),
|
|
38
|
+
});
|
|
39
|
+
// Generate individual action files
|
|
40
|
+
for (const action of allActions) {
|
|
41
|
+
const loadData = relevantSessionData[action] || {};
|
|
42
|
+
const saveData = sessionData[action] || {};
|
|
43
|
+
const saveCode = Mustache.render(saveActionTemplate, {
|
|
44
|
+
storeActions: Object.keys(saveData).map((key) => {
|
|
45
|
+
return {
|
|
46
|
+
key: key,
|
|
47
|
+
value: saveData[key],
|
|
48
|
+
};
|
|
49
|
+
}),
|
|
50
|
+
loadActions: Object.keys(loadData).map((key) => {
|
|
51
|
+
console.log(loadData[key]);
|
|
52
|
+
return {
|
|
53
|
+
key: loadData[key],
|
|
54
|
+
};
|
|
55
|
+
}),
|
|
56
|
+
action: action,
|
|
57
|
+
});
|
|
58
|
+
await writeAndFormatCode(this.rootPath, `./storage_actions/${action}.py`, saveCode, "python");
|
|
59
|
+
}
|
|
60
|
+
// Generate utility and interface files
|
|
61
|
+
await writeAndFormatCode(this.rootPath, "./utils/save_utils.py", sessionDataUtilsTemplate, "python");
|
|
62
|
+
await writeAndFormatCode(this.rootPath, "./types/storage_types.py", storageTypesTemplate, "python");
|
|
63
|
+
await writeAndFormatCode(this.rootPath, "./interfaces/storage_interface.py", storageInterfaceTemplate, "python");
|
|
64
|
+
await writeAndFormatCode(this.rootPath, "./storage_actions/__init__.py", indexCode, "python");
|
|
65
|
+
await writeAndFormatCode(this.rootPath, "./interfaces/__init__.py", "# Interfaces package", "python");
|
|
19
66
|
};
|
|
20
67
|
this.generateValidationCode = async () => {
|
|
21
68
|
const testConfig = this.validationConfig[ConfigSyntax.Tests];
|
|
@@ -35,6 +82,7 @@ export class PythonGenerator extends CodeGenerator {
|
|
|
35
82
|
}
|
|
36
83
|
};
|
|
37
84
|
this.generateCode = async (codeConfig) => {
|
|
85
|
+
this.codeConfig = codeConfig;
|
|
38
86
|
const jsonPathUtilsCode = readFileSync(path.resolve(__dirname, "./templates/json-path-utils.mustache"), "utf-8");
|
|
39
87
|
const validationUtils = readFileSync(path.resolve(__dirname, "./templates/validation-utils.mustache"), "utf-8");
|
|
40
88
|
const typesTemplate = readFileSync(path.resolve(__dirname, "./templates/test-config.mustache"), "utf-8");
|
|
@@ -61,6 +109,7 @@ export class PythonGenerator extends CodeGenerator {
|
|
|
61
109
|
await writeAndFormatCode(this.rootPath, "error.py", this.generateErrorFile(this.errorCodes), "python");
|
|
62
110
|
await writeAndFormatCode(this.rootPath, "__init__.py", this.generateIndexFile(apiNames, codeConfig.codeName), "python");
|
|
63
111
|
await new MarkdownDocGenerator(this.validationConfig, this.errorCodes, this.rootPath).generateCode();
|
|
112
|
+
await this.generateSessionDataCode();
|
|
64
113
|
};
|
|
65
114
|
this.generateTestFunction = async (testObject) => {
|
|
66
115
|
const template = readFileSync(path.resolve(__dirname, "./templates/test-object.mustache"), "utf-8");
|
|
@@ -89,8 +138,21 @@ export class PythonGenerator extends CodeGenerator {
|
|
|
89
138
|
const skipList = skip ? [skip] : undefined;
|
|
90
139
|
if (typeof testObject[TestObjectSyntax.Return] === "string") {
|
|
91
140
|
const returnStatement = compileInputToPy(testObject[TestObjectSyntax.Return]);
|
|
141
|
+
// Check if this is a stateful validation
|
|
142
|
+
let isStateFull = false;
|
|
143
|
+
for (const k in testObject) {
|
|
144
|
+
const value = testObject[k];
|
|
145
|
+
if (typeof value === "string") {
|
|
146
|
+
if (value.includes(`${ExternalDataSyntax}.`) &&
|
|
147
|
+
!value.includes("_SELF")) {
|
|
148
|
+
isStateFull = true;
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
92
153
|
return Mustache.render(template, {
|
|
93
154
|
isNested: false,
|
|
155
|
+
isStateFull: isStateFull,
|
|
94
156
|
returnStatement: returnStatement,
|
|
95
157
|
errorCode: testObject[TestObjectSyntax.ErrorCode] ?? 30000,
|
|
96
158
|
errorDescription: this.CreateErrorMarkdown(testObject, skipList),
|
|
@@ -117,6 +179,7 @@ export class PythonGenerator extends CodeGenerator {
|
|
|
117
179
|
nestedFunctions: indentedNestedFunctions,
|
|
118
180
|
names: names,
|
|
119
181
|
errorCode: testObject[TestObjectSyntax.ErrorCode] ?? 30000,
|
|
182
|
+
testName: testObject[TestObjectSyntax.Name],
|
|
120
183
|
TEST_OBJECT: `${JSON.stringify(testObject)}`,
|
|
121
184
|
});
|
|
122
185
|
}
|
|
@@ -203,9 +266,10 @@ ${errorsList}
|
|
|
203
266
|
.map((api) => `from .api_tests import ${api}`)
|
|
204
267
|
.join("\n");
|
|
205
268
|
importsCode += `\nfrom .types.test_config import ValidationConfig\n`;
|
|
269
|
+
importsCode += `\nfrom .storage_actions import perform_${functionName.toLowerCase()}_save, perform_${functionName.toLowerCase()}_load\n`;
|
|
206
270
|
const masterDoc = readFileSync(path.resolve(__dirname, "./templates/master-doc.mustache"), "utf-8");
|
|
207
271
|
const masterFunction = `
|
|
208
|
-
def perform_${functionName.toLowerCase()}(action, payload,config: ValidationConfig = None, external_data=None):
|
|
272
|
+
def perform_${functionName.toLowerCase()}(action, payload, config: ValidationConfig = None, external_data=None):
|
|
209
273
|
${masterDoc}
|
|
210
274
|
|
|
211
275
|
if external_data is None:
|
|
@@ -219,6 +283,7 @@ def perform_${functionName.toLowerCase()}(action, payload,config: ValidationConf
|
|
|
219
283
|
"standard_logs": False,
|
|
220
284
|
"_debug": False,
|
|
221
285
|
"hide_parent_errors": True,
|
|
286
|
+
"state_full_validations": False,
|
|
222
287
|
}
|
|
223
288
|
# Merge user config with default config
|
|
224
289
|
if config is None:
|
|
@@ -226,6 +291,21 @@ def perform_${functionName.toLowerCase()}(action, payload,config: ValidationConf
|
|
|
226
291
|
else:
|
|
227
292
|
config = {**default_config, **config}
|
|
228
293
|
|
|
294
|
+
if config.get("state_full_validations") and not config.get("store"):
|
|
295
|
+
raise Exception(
|
|
296
|
+
"State Full validations require a storage interface to be provided in the config."
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
if config.get("state_full_validations") and not config.get("unique_key"):
|
|
300
|
+
raise Exception(
|
|
301
|
+
"State Full validations require a unique_key to be provided in the config."
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
if config.get("state_full_validations"):
|
|
305
|
+
import asyncio
|
|
306
|
+
load_data = asyncio.run(perform_${functionName.toLowerCase()}_load(action, config["unique_key"], config["store"]))
|
|
307
|
+
external_data = {**load_data, **external_data}
|
|
308
|
+
|
|
229
309
|
input_data = {
|
|
230
310
|
"payload": normalized_payload,
|
|
231
311
|
"external_data": external_data,
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
from ..utils.json_path_utils import get_json_path
|
|
2
|
+
from ..utils.save_utils import save_data, load_function
|
|
3
|
+
from ..interfaces.storage_interface import StorageInterface
|
|
4
|
+
from ..types.storage_types import StorageConfig
|
|
5
|
+
import json
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
|
|
8
|
+
async def store_{{action}}(unique_prefix: str, payload: dict, store: StorageInterface, config: StorageConfig):
|
|
9
|
+
{{#storeActions}}
|
|
10
|
+
await save_function(payload, unique_prefix, "{{{key}}}", "{{{value}}}", store, config)
|
|
11
|
+
{{/storeActions}}
|
|
12
|
+
|
|
13
|
+
async def load_for_{{action}}(unique_prefix: str, store: StorageInterface):
|
|
14
|
+
return {
|
|
15
|
+
{{#loadActions}}
|
|
16
|
+
"{{{key}}}": await load_function(store, unique_prefix, "{{{key}}}"),
|
|
17
|
+
{{/loadActions}}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async def save_function(payload: dict, unique_prefix: str, key: str, path: str, store: StorageInterface, config: StorageConfig):
|
|
21
|
+
if path == "" or key == "_SELF":
|
|
22
|
+
return
|
|
23
|
+
|
|
24
|
+
value = get_json_path(payload, path)
|
|
25
|
+
data = {
|
|
26
|
+
"stored_from": "{{action}}_action",
|
|
27
|
+
"key_path": path,
|
|
28
|
+
"value": value,
|
|
29
|
+
"timestamp": datetime.now().isoformat()
|
|
30
|
+
}
|
|
31
|
+
data_string = json.dumps(data)
|
|
32
|
+
await save_data(unique_prefix, key, data_string, store, config)
|
|
33
|
+
|
|
34
|
+
async def load_function(store: StorageInterface, unique_prefix: str, key: str):
|
|
35
|
+
try:
|
|
36
|
+
value = await store.get_key(unique_prefix, key)
|
|
37
|
+
if not value:
|
|
38
|
+
return []
|
|
39
|
+
return json.loads(value)["value"]
|
|
40
|
+
except Exception:
|
|
41
|
+
return []
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{{#actions}}
|
|
2
|
+
from .{{{action}}} import store_{{{action}}}, load_for_{{{action}}}
|
|
3
|
+
{{/actions}}
|
|
4
|
+
from ..interfaces.storage_interface import StorageInterface
|
|
5
|
+
from ..types.storage_types import StorageConfig
|
|
6
|
+
from typing import Optional, Dict, Any
|
|
7
|
+
|
|
8
|
+
async def perform_{{functionName}}_save(action: str, unique_prefix: str, payload: Dict[Any, Any], store: StorageInterface, config: Optional[StorageConfig] = None):
|
|
9
|
+
complete_config: StorageConfig = {
|
|
10
|
+
"retry_attempts": 3,
|
|
11
|
+
"retry_delay_ms": 1000,
|
|
12
|
+
**(config or {})
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
{{#actions}}
|
|
16
|
+
{{#-first}}
|
|
17
|
+
if action == "{{{action}}}":
|
|
18
|
+
return await store_{{{action}}}(unique_prefix, payload, store, complete_config)
|
|
19
|
+
{{/-first}}
|
|
20
|
+
{{^-first}}
|
|
21
|
+
elif action == "{{{action}}}":
|
|
22
|
+
return await store_{{{action}}}(unique_prefix, payload, store, complete_config)
|
|
23
|
+
{{/-first}}
|
|
24
|
+
{{/actions}}
|
|
25
|
+
else:
|
|
26
|
+
raise Exception("Action not found")
|
|
27
|
+
|
|
28
|
+
async def perform_{{functionName}}_load(action: str, unique_prefix: str, store: StorageInterface):
|
|
29
|
+
{{#actions}}
|
|
30
|
+
{{#-first}}
|
|
31
|
+
if action == "{{{action}}}":
|
|
32
|
+
return await load_for_{{{action}}}(unique_prefix, store)
|
|
33
|
+
{{/-first}}
|
|
34
|
+
{{^-first}}
|
|
35
|
+
elif action == "{{{action}}}":
|
|
36
|
+
return await load_for_{{{action}}}(unique_prefix, store)
|
|
37
|
+
{{/-first}}
|
|
38
|
+
{{/actions}}
|
|
39
|
+
else:
|
|
40
|
+
raise Exception("Action not found")
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from ..interfaces.storage_interface import StorageInterface
|
|
2
|
+
from ..types.storage_types import StorageConfig
|
|
3
|
+
import asyncio
|
|
4
|
+
|
|
5
|
+
async def save_data(unique_prefix: str, key: str, save_data: str, store: StorageInterface, config: StorageConfig):
|
|
6
|
+
final_key = f"{unique_prefix}:{key}"
|
|
7
|
+
retry_times = config["retry_attempts"]
|
|
8
|
+
delay_ms = config["retry_delay_ms"]
|
|
9
|
+
attempts = 0
|
|
10
|
+
|
|
11
|
+
while attempts < retry_times:
|
|
12
|
+
try:
|
|
13
|
+
await store.save_key(unique_prefix, final_key, save_data)
|
|
14
|
+
return
|
|
15
|
+
except Exception as e:
|
|
16
|
+
attempts += 1
|
|
17
|
+
if attempts >= retry_times:
|
|
18
|
+
raise e
|
|
19
|
+
await asyncio.sleep(delay_ms / 1000.0) # Convert ms to seconds
|
|
20
|
+
|
|
21
|
+
def create_key(unique_prefix: str, key: str):
|
|
22
|
+
return f"{unique_prefix}:{key}"
|
package/dist/generator/generators/python/templates/storage-templates/storage-interface.mustache
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from typing import List
|
|
3
|
+
|
|
4
|
+
class StorageInterface(ABC):
|
|
5
|
+
"""
|
|
6
|
+
Storage interface for persisting and retrieving key-value data.
|
|
7
|
+
|
|
8
|
+
Purpose — Provides a standardized abstraction layer for storage operations
|
|
9
|
+
that can be implemented by different storage backends (Redis, Memory, File, etc.).
|
|
10
|
+
|
|
11
|
+
Implementation Notes:
|
|
12
|
+
- All operations are asynchronous and return coroutines
|
|
13
|
+
- Keys should be strings and values are stored as strings
|
|
14
|
+
- Implementations should handle serialization/deserialization as needed
|
|
15
|
+
- Prefix-based operations allow for namespacing and bulk operations
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
@abstractmethod
|
|
19
|
+
async def save_key(self, unique_prefix: str, key: str, value: str) -> None:
|
|
20
|
+
"""
|
|
21
|
+
Save a key-value pair to storage with a unique prefix for namespacing.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
unique_prefix: A unique identifier/namespace prefix to prevent key collisions
|
|
25
|
+
key: The unique identifier for the stored value within the prefix namespace
|
|
26
|
+
value: The string value to store
|
|
27
|
+
"""
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
@abstractmethod
|
|
31
|
+
async def get_key(self, unique_prefix: str, key: str) -> str:
|
|
32
|
+
"""
|
|
33
|
+
Retrieve a value by its key from storage within a specific namespace.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
unique_prefix: The unique identifier/namespace prefix used when storing
|
|
37
|
+
key: The unique identifier for the value to retrieve within the prefix namespace
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
The stored value as a string
|
|
41
|
+
|
|
42
|
+
Raises:
|
|
43
|
+
Exception if the key does not exist
|
|
44
|
+
"""
|
|
45
|
+
pass
|
|
46
|
+
|
|
47
|
+
@abstractmethod
|
|
48
|
+
async def delete_key(self, unique_prefix: str, key: str) -> None:
|
|
49
|
+
"""
|
|
50
|
+
Remove a key-value pair from storage within a specific namespace.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
unique_prefix: The unique identifier/namespace prefix used when storing
|
|
54
|
+
key: The unique identifier for the value to delete within the prefix namespace
|
|
55
|
+
"""
|
|
56
|
+
pass
|
|
57
|
+
|
|
58
|
+
@abstractmethod
|
|
59
|
+
async def list_keys(self, unique_prefix: str) -> List[str]:
|
|
60
|
+
"""
|
|
61
|
+
List all keys within a specific namespace/prefix.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
unique_prefix: The unique identifier/namespace prefix to list keys from
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
A list of keys within that namespace
|
|
68
|
+
"""
|
|
69
|
+
pass
|
|
70
|
+
|
|
71
|
+
@abstractmethod
|
|
72
|
+
async def clear_storage(self) -> None:
|
|
73
|
+
"""
|
|
74
|
+
Clear all stored data from the storage backend.
|
|
75
|
+
|
|
76
|
+
Warning: This operation is destructive and cannot be undone.
|
|
77
|
+
Use with caution, especially in production environments.
|
|
78
|
+
"""
|
|
79
|
+
pass
|
|
80
|
+
|
|
81
|
+
@abstractmethod
|
|
82
|
+
async def key_exists(self, unique_prefix: str, key: str) -> bool:
|
|
83
|
+
"""
|
|
84
|
+
Check if a key exists in storage within a specific namespace.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
unique_prefix: The unique identifier/namespace prefix to check within
|
|
88
|
+
key: The unique identifier to check for existence within the prefix namespace
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
True if the key exists, False otherwise
|
|
92
|
+
"""
|
|
93
|
+
pass
|
|
@@ -12,10 +12,16 @@ class ValidationConfig(TypedDict, total=False):
|
|
|
12
12
|
only_invalid (bool): If True, only invalid results will be returned.
|
|
13
13
|
hide_parent_errors (Optional[bool]): If True, parent errors will be hidden.
|
|
14
14
|
_debug (Optional[bool]): If True, debug mode will be enabled.
|
|
15
|
+
state_full_validations (Optional[bool]): If True, stateful validations will be enabled.
|
|
16
|
+
store (Optional[Any]): Storage interface for stateful validations.
|
|
17
|
+
unique_key (Optional[str]): Unique key for stateful validations.
|
|
15
18
|
"""
|
|
16
19
|
only_invalid: Optional[bool]
|
|
17
20
|
hide_parent_errors: Optional[bool]
|
|
18
21
|
_debug: Optional[bool]
|
|
22
|
+
state_full_validations: Optional[bool]
|
|
23
|
+
store: Optional[Any]
|
|
24
|
+
unique_key: Optional[str]
|
|
19
25
|
|
|
20
26
|
# Input structure for validation functions
|
|
21
27
|
ValidationInput = Dict[str, Any]
|
|
@@ -17,10 +17,18 @@
|
|
|
17
17
|
valid = all(r["valid"] for r in sub_results)
|
|
18
18
|
{{/isNested}}
|
|
19
19
|
{{^isNested}}
|
|
20
|
+
{{#isStateFull}}
|
|
21
|
+
validate = True
|
|
22
|
+
if input_data["config"].get("state_full_validations") == True:
|
|
23
|
+
validate = {{{returnStatement}}}
|
|
24
|
+
{{/isStateFull}}
|
|
25
|
+
|
|
26
|
+
{{^isStateFull}}
|
|
20
27
|
validate = {{{returnStatement}}}
|
|
28
|
+
{{/isStateFull}}
|
|
21
29
|
|
|
22
30
|
if not validate:
|
|
23
|
-
del {{testName}}_obj["_EXTERNAL"]
|
|
31
|
+
# del {{testName}}_obj["_EXTERNAL"]
|
|
24
32
|
return [{
|
|
25
33
|
"test_name": "{{testName}}",
|
|
26
34
|
"valid": False,
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import payloadUtils from "../utils/json-path-utils";
|
|
2
|
+
import saveUtils from "../utils/save-utils";
|
|
3
|
+
import StorageInterface from "../interfaces/storage-interface";
|
|
4
|
+
import { StorageConfig } from "../types/storage-types";
|
|
5
|
+
|
|
6
|
+
export async function store_{{action}}(
|
|
7
|
+
uniquePrefix: string,
|
|
8
|
+
payload: any,
|
|
9
|
+
store: StorageInterface,
|
|
10
|
+
config: StorageConfig
|
|
11
|
+
){
|
|
12
|
+
{{#storeActions}}
|
|
13
|
+
await saveFunction(payload,uniquePrefix,"{{{key}}}", "{{{value}}}", store, config);
|
|
14
|
+
{{/storeActions}}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function loadFor_{{action}}(
|
|
18
|
+
uniquePrefix: string,
|
|
19
|
+
store: StorageInterface
|
|
20
|
+
) {
|
|
21
|
+
return {
|
|
22
|
+
{{#loadActions}}
|
|
23
|
+
{{{key}}}: await loadFunction(store, uniquePrefix, "{{{key}}}"),
|
|
24
|
+
{{/loadActions}}
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function saveFunction(payload: any,uniquePrefix: string, key: string, path: string, store: StorageInterface,
|
|
29
|
+
config: StorageConfig): Promise<void> {
|
|
30
|
+
if(path === "" || key === "_SELF"){
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
const value = payloadUtils.getJsonPath(payload, path);
|
|
34
|
+
const data = {
|
|
35
|
+
stored_from: "{{action}}_action",
|
|
36
|
+
key_path: path,
|
|
37
|
+
value: value,
|
|
38
|
+
timestamp: new Date().toISOString(),
|
|
39
|
+
};
|
|
40
|
+
const dataString = JSON.stringify(data);
|
|
41
|
+
await saveUtils.saveData(uniquePrefix, key, dataString, store, config);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function loadFunction(
|
|
45
|
+
store: StorageInterface,
|
|
46
|
+
uniquePrefix: string,
|
|
47
|
+
key: string
|
|
48
|
+
): Promise<any[] | []> {
|
|
49
|
+
try {
|
|
50
|
+
const value = await store.getKey(uniquePrefix, key);
|
|
51
|
+
if (!value) {
|
|
52
|
+
return [];
|
|
53
|
+
}
|
|
54
|
+
return JSON.parse(value).value;
|
|
55
|
+
} catch (err) {
|
|
56
|
+
return [];
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{{#actions}}
|
|
2
|
+
import { store_{{{action}}}, loadFor_{{{action}}} } from './{{{action}}}'
|
|
3
|
+
{{/actions}}
|
|
4
|
+
import StorageInterface from "../interfaces/storage-interface";
|
|
5
|
+
import { StorageConfig } from "../types/storage-types";
|
|
6
|
+
|
|
7
|
+
export async function perform{{functionName}}Save(action: string,uniquePrefix: string, payload: any, store: StorageInterface,
|
|
8
|
+
config?: Partial<StorageConfig>){
|
|
9
|
+
|
|
10
|
+
const completeConfig : StorageConfig = {
|
|
11
|
+
...{
|
|
12
|
+
retryAttempts: 3,
|
|
13
|
+
retryDelayMs: 1000,
|
|
14
|
+
},
|
|
15
|
+
...config
|
|
16
|
+
}
|
|
17
|
+
switch(action){
|
|
18
|
+
{{#actions}}
|
|
19
|
+
case '{{{action}}}':
|
|
20
|
+
return await store_{{{action}}}(uniquePrefix,payload,store,completeConfig);
|
|
21
|
+
{{/actions}}
|
|
22
|
+
default:
|
|
23
|
+
throw new Error('Action not found');
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
export async function perform{{functionName}}Load(action: string,uniquePrefix: string, store: StorageInterface){
|
|
29
|
+
switch(action){
|
|
30
|
+
{{#actions}}
|
|
31
|
+
case '{{{action}}}':
|
|
32
|
+
return await loadFor_{{{action}}}(uniquePrefix,store);
|
|
33
|
+
{{/actions}}
|
|
34
|
+
default:
|
|
35
|
+
throw new Error('Action not found');
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import StorageInterface from "../interfaces/storage-interface";
|
|
2
|
+
import { StorageConfig } from "../types/storage-types";
|
|
3
|
+
async function saveData(
|
|
4
|
+
uniquePrefix: string,
|
|
5
|
+
key: string,
|
|
6
|
+
saveData: string,
|
|
7
|
+
store: StorageInterface,
|
|
8
|
+
config: StorageConfig
|
|
9
|
+
){
|
|
10
|
+
const finalKey = `${uniquePrefix}:${key}`;
|
|
11
|
+
const retryTimes = config.retryAttempts;
|
|
12
|
+
const delayMs = config.retryDelayMs;
|
|
13
|
+
let attempts = 0;
|
|
14
|
+
while(attempts < retryTimes){
|
|
15
|
+
try{
|
|
16
|
+
await store.saveKey(uniquePrefix, finalKey, saveData);
|
|
17
|
+
return;
|
|
18
|
+
}catch(e){
|
|
19
|
+
attempts++;
|
|
20
|
+
if(attempts >= retryTimes){
|
|
21
|
+
throw e;
|
|
22
|
+
}
|
|
23
|
+
await new Promise(res => setTimeout(res,delayMs));
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function createKey(
|
|
30
|
+
uniquePrefix: string,
|
|
31
|
+
key: string
|
|
32
|
+
){
|
|
33
|
+
return `${uniquePrefix}:${key}`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export default {
|
|
37
|
+
saveData,
|
|
38
|
+
createKey
|
|
39
|
+
}
|
package/dist/generator/generators/typescript/templates/storage-templates/storage-interface.mustache
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Storage interface for persisting and retrieving key-value data.
|
|
3
|
+
*
|
|
4
|
+
* @remarks
|
|
5
|
+
* **Purpose** — Provides a standardized abstraction layer for storage operations
|
|
6
|
+
* that can be implemented by different storage backends (Redis, Memory, File, etc.).
|
|
7
|
+
*
|
|
8
|
+
* **Implementation Notes**:
|
|
9
|
+
* - All operations are asynchronous and return Promises
|
|
10
|
+
* - Keys should be strings and values are stored as strings
|
|
11
|
+
* - Implementations should handle serialization/deserialization as needed
|
|
12
|
+
* - Prefix-based operations allow for namespacing and bulk operations
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* class RedisStorage implements StorageInterface {
|
|
17
|
+
* async saveKey(uniquePrefix: string, key: string, value: string): Promise<void> {
|
|
18
|
+
* const fullKey = `${uniquePrefix}:${key}`;
|
|
19
|
+
* await this.redisClient.set(fullKey, value);
|
|
20
|
+
* }
|
|
21
|
+
* // ... other methods
|
|
22
|
+
* }
|
|
23
|
+
*
|
|
24
|
+
* const storage = new RedisStorage();
|
|
25
|
+
* await storage.saveKey("app1", "user:123", JSON.stringify(userData));
|
|
26
|
+
* const user = JSON.parse(await storage.getKey("app1", "user:123"));
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export default interface StorageInterface {
|
|
30
|
+
/**
|
|
31
|
+
* Save a key-value pair to storage with a unique prefix for namespacing.
|
|
32
|
+
*
|
|
33
|
+
* @param uniquePrefix - A unique identifier/namespace prefix to prevent key collisions
|
|
34
|
+
* @param key - The unique identifier for the stored value within the prefix namespace
|
|
35
|
+
* @param value - The string value to store
|
|
36
|
+
* @returns Promise that resolves when the operation completes
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```ts
|
|
40
|
+
* await storage.saveKey("app1", "session:abc123", JSON.stringify(sessionData));
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
saveKey(uniquePrefix: string, key: string, value: string): Promise<void>;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Retrieve a value by its key from storage within a specific namespace.
|
|
47
|
+
*
|
|
48
|
+
* @param uniquePrefix - The unique identifier/namespace prefix used when storing
|
|
49
|
+
* @param key - The unique identifier for the value to retrieve within the prefix namespace
|
|
50
|
+
* @returns Promise that resolves to the stored value
|
|
51
|
+
* @throws Should throw an error if the key does not exist
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* ```ts
|
|
55
|
+
* const sessionData = await storage.getKey("app1", "session:abc123");
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
getKey(uniquePrefix: string, key: string): Promise<string>;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Remove a key-value pair from storage within a specific namespace.
|
|
62
|
+
*
|
|
63
|
+
* @param uniquePrefix - The unique identifier/namespace prefix used when storing
|
|
64
|
+
* @param key - The unique identifier for the value to delete within the prefix namespace
|
|
65
|
+
* @returns Promise that resolves when the deletion completes
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* ```ts
|
|
69
|
+
* await storage.deleteKey("app1", "session:abc123");
|
|
70
|
+
* ```
|
|
71
|
+
*/
|
|
72
|
+
deleteKey(uniquePrefix: string, key: string): Promise<void>;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* List all keys within a specific namespace/prefix.
|
|
76
|
+
*
|
|
77
|
+
* @param uniquePrefix - The unique identifier/namespace prefix to list keys from
|
|
78
|
+
* @returns Promise that resolves to an array of keys within that namespace
|
|
79
|
+
*
|
|
80
|
+
* @example
|
|
81
|
+
* ```ts
|
|
82
|
+
* const app1Keys = await storage.listKeys("app1");
|
|
83
|
+
* // Returns keys stored under the "app1" namespace
|
|
84
|
+
* // e.g., ["session:abc123", "user:456", "config:settings"]
|
|
85
|
+
* ```
|
|
86
|
+
*/
|
|
87
|
+
listKeys(uniquePrefix: string): Promise<string[]>;
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Clear all stored data from the storage backend.
|
|
91
|
+
*
|
|
92
|
+
* @remarks
|
|
93
|
+
* **Warning** — This operation is destructive and cannot be undone.
|
|
94
|
+
* Use with caution, especially in production environments.
|
|
95
|
+
*
|
|
96
|
+
* @returns Promise that resolves when all data has been cleared
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* ```ts
|
|
100
|
+
* await storage.clearStorage(); // All data is now removed
|
|
101
|
+
* ```
|
|
102
|
+
*/
|
|
103
|
+
clearStorage(): Promise<void>;
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Check if a key exists in storage within a specific namespace.
|
|
107
|
+
*
|
|
108
|
+
* @param uniquePrefix - The unique identifier/namespace prefix to check within
|
|
109
|
+
* @param key - The unique identifier to check for existence within the prefix namespace
|
|
110
|
+
* @returns Promise that resolves to true if the key exists, false otherwise
|
|
111
|
+
*
|
|
112
|
+
* @example
|
|
113
|
+
* ```ts
|
|
114
|
+
* const exists = await storage.keyExists("app1", "user:123");
|
|
115
|
+
* if (exists) {
|
|
116
|
+
* const userData = await storage.getKey("app1", "user:123");
|
|
117
|
+
* }
|
|
118
|
+
* ```
|
|
119
|
+
*/
|
|
120
|
+
keyExists(uniquePrefix: string, key: string): Promise<boolean>;
|
|
121
|
+
}
|
|
@@ -1,17 +1,25 @@
|
|
|
1
|
+
import StorageInterface from "../interfaces/storage-interface";
|
|
2
|
+
|
|
3
|
+
|
|
1
4
|
/**
|
|
2
5
|
* Configuration options for running validation routines.
|
|
3
6
|
*
|
|
4
7
|
* @property onlyInvalid - If true, only invalid results will be returned.
|
|
8
|
+
* @property stateFullValidations - If true, enables stateful validations that may depend on previous data.
|
|
9
|
+
* @property uniqueKey - Optional. A unique key for the validation instance.
|
|
10
|
+
* @property store - Optional. An implementation of StorageInterface for persisting data across validations.
|
|
5
11
|
* @property hideParentErrors - Optional. Hides nested error details if set to true.
|
|
6
12
|
* @property _debug - Optional. Enables debug mode for additional diagnostic information.
|
|
7
13
|
*/
|
|
8
14
|
export interface ValidationConfig {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
15
|
+
stateFullValidations: boolean;
|
|
16
|
+
uniqueKey?: string;
|
|
17
|
+
store?: StorageInterface;
|
|
18
|
+
onlyInvalid: boolean;
|
|
19
|
+
hideParentErrors: boolean;
|
|
20
|
+
_debug: boolean;
|
|
12
21
|
}
|
|
13
22
|
|
|
14
|
-
|
|
15
23
|
/**
|
|
16
24
|
* Represents the output of a validation run.
|
|
17
25
|
*
|
|
@@ -49,15 +57,14 @@ export type validationOutput = {
|
|
|
49
57
|
}[];
|
|
50
58
|
|
|
51
59
|
|
|
52
|
-
|
|
53
|
-
|
|
60
|
+
|
|
61
|
+
export type ExternalData = {
|
|
54
62
|
{{#externalData}}
|
|
55
63
|
{{name}}?: string[];
|
|
56
64
|
{{/externalData}}
|
|
57
|
-
|
|
58
|
-
|
|
65
|
+
_SELF?: any;
|
|
66
|
+
};
|
|
59
67
|
|
|
60
|
-
export type ExternalData = {}
|
|
61
68
|
|
|
62
69
|
export type validationInput = {
|
|
63
70
|
payload: any;
|
|
@@ -20,10 +20,20 @@ valid = subResults.every((r) => r.valid);
|
|
|
20
20
|
{{/isNested}}
|
|
21
21
|
|
|
22
22
|
{{^isNested}}
|
|
23
|
+
|
|
24
|
+
{{#isStateFull}}
|
|
25
|
+
let validate = true;
|
|
26
|
+
if(input.config.stateFullValidations === true){
|
|
27
|
+
validate = {{{returnStatement}}}
|
|
28
|
+
}
|
|
29
|
+
{{/isStateFull}}
|
|
30
|
+
|
|
31
|
+
{{^isStateFull}}
|
|
23
32
|
const validate = {{{returnStatement}}}
|
|
33
|
+
{{/isStateFull}}
|
|
24
34
|
|
|
25
35
|
if(!validate){
|
|
26
|
-
delete testObj._EXTERNAL;
|
|
36
|
+
// delete testObj._EXTERNAL;
|
|
27
37
|
return [{
|
|
28
38
|
testName: "{{testName}}",
|
|
29
39
|
valid: false,
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { TestObject } from "../../../types/config-types.js";
|
|
2
2
|
import { CodeGenerator, CodeGeneratorProps } from "../classes/abstract-generator.js";
|
|
3
3
|
export declare class TypescriptGenerator extends CodeGenerator {
|
|
4
|
-
|
|
4
|
+
codeConfig: CodeGeneratorProps | undefined;
|
|
5
|
+
generateSessionDataCode: () => Promise<void>;
|
|
5
6
|
generateValidationCode: () => Promise<void>;
|
|
6
7
|
generateCode: (codeConfig: CodeGeneratorProps) => Promise<void>;
|
|
7
8
|
generateTestFunction: (testObject: TestObject) => Promise<{
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { readFileSync } from "fs";
|
|
2
2
|
import path from "path";
|
|
3
3
|
import { fileURLToPath } from "url";
|
|
4
|
-
import { ConfigSyntax, TestObjectSyntax } from "../../../constants/syntax.js";
|
|
4
|
+
import { ConfigSyntax, ExternalDataSyntax, TestObjectSyntax, } from "../../../constants/syntax.js";
|
|
5
5
|
import Mustache from "mustache";
|
|
6
6
|
import { markdownMessageGenerator } from "../documentation/markdown-message-generator.js";
|
|
7
7
|
import { getVariablesFromTest as extractVariablesFromText } from "../../../utils/general-utils/test-object-utils.js";
|
|
@@ -10,13 +10,64 @@ import { compileInputToTs } from "./ts-ast.js";
|
|
|
10
10
|
import { CodeGenerator, } from "../classes/abstract-generator.js";
|
|
11
11
|
import { writeAndFormatCode } from "../../../utils/fs-utils.js";
|
|
12
12
|
import { MarkdownDocGenerator } from "../documentation/md-generator.js";
|
|
13
|
+
import { collectLoadData } from "../../../utils/config-utils/load-variables.js";
|
|
13
14
|
const __filename = fileURLToPath(import.meta.url);
|
|
14
15
|
const __dirname = path.dirname(__filename);
|
|
15
16
|
export class TypescriptGenerator extends CodeGenerator {
|
|
16
17
|
constructor() {
|
|
17
18
|
super(...arguments);
|
|
18
19
|
this.generateSessionDataCode = async () => {
|
|
19
|
-
|
|
20
|
+
if (!this.codeConfig) {
|
|
21
|
+
throw new Error("Code config not set. Please call generateCode first.");
|
|
22
|
+
}
|
|
23
|
+
const sessionData = this.validationConfig[ConfigSyntax.SessionData];
|
|
24
|
+
const tests = this.validationConfig[ConfigSyntax.Tests];
|
|
25
|
+
const relevantSessionData = {};
|
|
26
|
+
collectLoadData(tests, relevantSessionData);
|
|
27
|
+
console.log("Relevant Session Data for Loading:", relevantSessionData);
|
|
28
|
+
const actions = Object.keys(sessionData);
|
|
29
|
+
const sessionDataUtilsTemplate = readFileSync(path.resolve(__dirname, "./templates/storage-templates/save-utils.mustache"), "utf-8");
|
|
30
|
+
const storageInterfaceTemplate = readFileSync(path.resolve(__dirname, "./templates/storage-templates/storage-interface.mustache"), "utf-8");
|
|
31
|
+
const storageTypesTemplate = readFileSync(path.resolve(__dirname, "./templates/storage-templates/storage-types.mustache"), "utf-8");
|
|
32
|
+
const indexTemplate = readFileSync(path.resolve(__dirname, "./templates/storage-templates/index.mustache"), "utf-8");
|
|
33
|
+
const saveActionTemplate = readFileSync(path.resolve(__dirname, "./templates/storage-templates/api-save.mustache"), "utf-8");
|
|
34
|
+
const allActions = Object.keys(tests);
|
|
35
|
+
const indexCode = Mustache.render(indexTemplate, {
|
|
36
|
+
actions: Array.from(allActions).map((action) => {
|
|
37
|
+
return { action: action };
|
|
38
|
+
}),
|
|
39
|
+
functionName: this.codeConfig.codeName.replace(/[^a-zA-Z0-9_]/g, ""),
|
|
40
|
+
});
|
|
41
|
+
/*
|
|
42
|
+
storeActions: {
|
|
43
|
+
key: string;
|
|
44
|
+
value: string;
|
|
45
|
+
}[]
|
|
46
|
+
*/
|
|
47
|
+
for (const action of allActions) {
|
|
48
|
+
const loadData = relevantSessionData[action] || {};
|
|
49
|
+
const saveData = sessionData[action] || {};
|
|
50
|
+
const saveCode = Mustache.render(saveActionTemplate, {
|
|
51
|
+
storeActions: Object.keys(saveData).map((key) => {
|
|
52
|
+
return {
|
|
53
|
+
key: key,
|
|
54
|
+
value: saveData[key],
|
|
55
|
+
};
|
|
56
|
+
}),
|
|
57
|
+
loadActions: Object.keys(loadData).map((key) => {
|
|
58
|
+
console.log(loadData[key]);
|
|
59
|
+
return {
|
|
60
|
+
key: loadData[key],
|
|
61
|
+
};
|
|
62
|
+
}),
|
|
63
|
+
action: action,
|
|
64
|
+
});
|
|
65
|
+
await writeAndFormatCode(this.rootPath, `./storage-actions/${action}.ts`, saveCode, "typescript");
|
|
66
|
+
}
|
|
67
|
+
await writeAndFormatCode(this.rootPath, "./utils/save-utils.ts", sessionDataUtilsTemplate, "typescript");
|
|
68
|
+
await writeAndFormatCode(this.rootPath, "./types/storage-types.ts", storageTypesTemplate, "typescript");
|
|
69
|
+
await writeAndFormatCode(this.rootPath, "./interfaces/storage-interface.ts", storageInterfaceTemplate, "typescript");
|
|
70
|
+
await writeAndFormatCode(this.rootPath, "./storage-actions/index.ts", indexCode, "typescript");
|
|
20
71
|
};
|
|
21
72
|
this.generateValidationCode = async () => {
|
|
22
73
|
const testConfig = this.validationConfig[ConfigSyntax.Tests];
|
|
@@ -36,6 +87,7 @@ export class TypescriptGenerator extends CodeGenerator {
|
|
|
36
87
|
}
|
|
37
88
|
};
|
|
38
89
|
this.generateCode = async (codeConfig) => {
|
|
90
|
+
this.codeConfig = codeConfig;
|
|
39
91
|
const jsonPathUtilsCode = readFileSync(path.resolve(__dirname, "./templates/json-path-utils.mustache"), "utf-8");
|
|
40
92
|
const validtionUtils = readFileSync(path.resolve(__dirname, "./templates/validation-utils.mustache"), "utf-8");
|
|
41
93
|
const typesTemplate = readFileSync(path.resolve(__dirname, "./templates/test-config.mustache"), "utf-8");
|
|
@@ -51,6 +103,7 @@ export class TypescriptGenerator extends CodeGenerator {
|
|
|
51
103
|
await writeAndFormatCode(this.rootPath, "error.ts", this.generateErrorFile(this.errorCodes), "typescript");
|
|
52
104
|
await writeAndFormatCode(this.rootPath, "index.ts", this.generateIndexFile(Object.keys(this.validationConfig[ConfigSyntax.Tests]), codeConfig.codeName), "typescript");
|
|
53
105
|
await new MarkdownDocGenerator(this.validationConfig, this.errorCodes, this.rootPath).generateCode();
|
|
106
|
+
await this.generateSessionDataCode();
|
|
54
107
|
};
|
|
55
108
|
this.generateTestFunction = async (testObject) => {
|
|
56
109
|
const template = readFileSync(path.resolve(__dirname, "./templates/test-object.mustache"), "utf-8");
|
|
@@ -79,8 +132,20 @@ export class TypescriptGenerator extends CodeGenerator {
|
|
|
79
132
|
const skipList = skip ? [skip] : undefined;
|
|
80
133
|
if (typeof testObject[TestObjectSyntax.Return] === "string") {
|
|
81
134
|
const returnStatement = compileInputToTs(testObject[TestObjectSyntax.Return]);
|
|
135
|
+
let isStateFull = false;
|
|
136
|
+
for (const k in testObject) {
|
|
137
|
+
const value = testObject[k];
|
|
138
|
+
if (typeof value === "string") {
|
|
139
|
+
if (value.includes(`${ExternalDataSyntax}.`) &&
|
|
140
|
+
!value.includes("_SELF")) {
|
|
141
|
+
isStateFull = true;
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
82
146
|
return Mustache.render(template, {
|
|
83
147
|
isNested: false,
|
|
148
|
+
isStateFull: isStateFull,
|
|
84
149
|
returnStatement: returnStatement,
|
|
85
150
|
errorCode: testObject[TestObjectSyntax.ErrorCode] ?? 30000,
|
|
86
151
|
errorDescription: this.CreateErrorMarkdown(testObject, skipList),
|
|
@@ -155,13 +220,14 @@ export class TypescriptGenerator extends CodeGenerator {
|
|
|
155
220
|
}
|
|
156
221
|
getExternalKeys() {
|
|
157
222
|
const apis = Object.keys(this.validationConfig[ConfigSyntax.SessionData]);
|
|
158
|
-
|
|
223
|
+
let result = [];
|
|
159
224
|
for (const api of apis) {
|
|
160
225
|
const keys = Object.keys(this.validationConfig[ConfigSyntax.SessionData][api]);
|
|
161
226
|
for (const key of keys) {
|
|
162
227
|
result.push({ name: key });
|
|
163
228
|
}
|
|
164
229
|
}
|
|
230
|
+
result = result.filter((v) => v.name !== "_SELF");
|
|
165
231
|
return result;
|
|
166
232
|
}
|
|
167
233
|
generateIndexFile(apis, functionName = "L1Validations") {
|
|
@@ -171,15 +237,34 @@ export class TypescriptGenerator extends CodeGenerator {
|
|
|
171
237
|
.join("\n");
|
|
172
238
|
importsCode += `\nimport { ValidationConfig,validationOutput } from "./types/test-config";`;
|
|
173
239
|
importsCode += `\nimport normalizeKeys from "./utils/json-normalizer";`;
|
|
240
|
+
importsCode += `\nimport { perform${functionName}Save, perform${functionName}Load} from "./storage-actions";`;
|
|
241
|
+
importsCode += `\nimport StorageInterface from "./interfaces/storage-interface";`;
|
|
174
242
|
const masterTemplate = readFileSync(path.resolve(__dirname, "./templates/index.mustache"), "utf-8");
|
|
175
243
|
const masterFunction = `
|
|
176
244
|
export function perform${functionName}(action: string, payload: any, config?: Partial<ValidationConfig>, externalData: any = {}) {
|
|
177
245
|
const completeConfig: ValidationConfig = {
|
|
178
|
-
...{ onlyInvalid: true, standardLogs: false, hideParentErrors: true, _debug: false },
|
|
246
|
+
...{ onlyInvalid: true, standardLogs: false, hideParentErrors: true, stateFullValidations: false, _debug: false },
|
|
179
247
|
...config,
|
|
180
248
|
};
|
|
249
|
+
|
|
250
|
+
if (completeConfig.stateFullValidations && !completeConfig.store) {
|
|
251
|
+
throw new Error(
|
|
252
|
+
"State Full validations require a storage interface to be provided in the config."
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
if( completeConfig.stateFullValidations && !completeConfig.uniqueKey) {
|
|
256
|
+
throw new Error(
|
|
257
|
+
"State Full validations require a uniqueKey to be provided in the config."
|
|
258
|
+
);
|
|
259
|
+
}
|
|
181
260
|
const normalizedPayload = normalizeKeys(JSON.parse(JSON.stringify(payload)));
|
|
182
261
|
externalData._SELF = normalizedPayload;
|
|
262
|
+
if (completeConfig.stateFullValidations) {
|
|
263
|
+
externalData = {
|
|
264
|
+
...performL1_validationsLoad(action, completeConfig.uniqueKey!, completeConfig.store!),
|
|
265
|
+
...externalData,
|
|
266
|
+
};
|
|
267
|
+
}
|
|
183
268
|
switch (action) {
|
|
184
269
|
${apis
|
|
185
270
|
.map((api) => `case "${api}": return ${api}({
|
|
@@ -191,7 +276,11 @@ export class TypescriptGenerator extends CodeGenerator {
|
|
|
191
276
|
default:
|
|
192
277
|
throw new Error("Action not found");
|
|
193
278
|
}
|
|
194
|
-
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
export {perform${functionName}Save, perform${functionName}Load, StorageInterface};
|
|
282
|
+
|
|
283
|
+
`;
|
|
195
284
|
return Mustache.render(masterTemplate, {
|
|
196
285
|
importsCode: importsCode,
|
|
197
286
|
masterFunction: masterFunction,
|
|
@@ -11,7 +11,17 @@ export class ConfigValidator {
|
|
|
11
11
|
throw new Error(`SessionData not found in config`);
|
|
12
12
|
const sessionData = this.config[ConfigSyntax.SessionData];
|
|
13
13
|
const tests = this.config[ConfigSyntax.Tests];
|
|
14
|
-
|
|
14
|
+
const sessionDataValidator = new SessionDataValidator(`${this.validationPath}/${ConfigSyntax.SessionData}`, sessionData);
|
|
15
|
+
await sessionDataValidator.validate();
|
|
16
|
+
for (const api in sessionData) {
|
|
17
|
+
const paths = this.stringJsonPaths[api];
|
|
18
|
+
for (const key in sessionData[api]) {
|
|
19
|
+
const value = sessionData[api][key];
|
|
20
|
+
if (typeof value === "string") {
|
|
21
|
+
sessionDataValidator.validateApiPath(paths, value, api, key);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
15
25
|
const externalVariables = getExternalVariables(sessionData);
|
|
16
26
|
for (const api in tests) {
|
|
17
27
|
const testList = tests[api];
|
|
@@ -6,4 +6,5 @@ export declare class SessionDataValidator implements IValidator {
|
|
|
6
6
|
constructor(validtionPath: string, sessionData: Record<string, SessionDataApi>);
|
|
7
7
|
validate: () => Promise<void>;
|
|
8
8
|
private validateSessionData;
|
|
9
|
+
validateApiPath(apiPaths: string[], path: string, api: string, key: string): void;
|
|
9
10
|
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { ConfigSyntax } from "../../../constants/syntax.js";
|
|
1
2
|
import { isPrimitive } from "../../../utils/general-utils/validation-utils.js";
|
|
3
|
+
import { isValidJsonPath } from "../../../utils/json-path-utils/paths.js";
|
|
2
4
|
export class SessionDataValidator {
|
|
3
5
|
constructor(validtionPath, sessionData) {
|
|
4
6
|
this.validate = async () => {
|
|
@@ -32,4 +34,12 @@ export class SessionDataValidator {
|
|
|
32
34
|
}
|
|
33
35
|
});
|
|
34
36
|
}
|
|
37
|
+
validateApiPath(apiPaths, path, api, key) {
|
|
38
|
+
if (!isValidJsonPath(path)) {
|
|
39
|
+
throw new Error(`Invalid ${ConfigSyntax.SessionData} path at ${path}: Path should be a valid JSONPath expression for ${api} and key: ${key}`);
|
|
40
|
+
}
|
|
41
|
+
if (!apiPaths.includes(path)) {
|
|
42
|
+
throw new Error(`Invalid ${ConfigSyntax.SessionData} path at ${path}: Path should be a valid path which returns a linear array for ${api} and key: ${key}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
35
45
|
}
|
package/dist/index.js
CHANGED
|
@@ -1,46 +1,2 @@
|
|
|
1
1
|
import { ConfigCompiler } from "./generator/config-compiler.js";
|
|
2
2
|
export { ConfigCompiler };
|
|
3
|
-
// import { readFileSync } from "fs";
|
|
4
|
-
// import path from "path";
|
|
5
|
-
// import { fileURLToPath } from "url";
|
|
6
|
-
// const __filename = fileURLToPath(import.meta.url);
|
|
7
|
-
// const __dirname = path.dirname(__filename);
|
|
8
|
-
// import { SupportedLanguages } from "./types/compiler-types.js";
|
|
9
|
-
// const main = async () => {
|
|
10
|
-
// const compiler = new ConfigCompiler(SupportedLanguages.Python);
|
|
11
|
-
// const buildPath = path.resolve(__dirname, "../samples/build.yaml");
|
|
12
|
-
// const valConfigPath = path.resolve(
|
|
13
|
-
// __dirname,
|
|
14
|
-
// "../samples/validation-config.json"
|
|
15
|
-
// );
|
|
16
|
-
// const buildYaml = readFileSync(buildPath, "utf-8");
|
|
17
|
-
// const valConfig = JSON.parse(readFileSync(valConfigPath, "utf-8"));
|
|
18
|
-
// // await compiler.initialize(buildYaml);
|
|
19
|
-
// // await compiler.generateCode(
|
|
20
|
-
// // valConfig,
|
|
21
|
-
// // "L1_validations",
|
|
22
|
-
// // false,
|
|
23
|
-
// // "./alpha/python/"
|
|
24
|
-
// // );
|
|
25
|
-
// // const compilerTy = new ConfigCompiler(SupportedLanguages.Typescript);
|
|
26
|
-
// // await compilerTy.initialize(buildYaml);
|
|
27
|
-
// // await compilerTy.generateCode(
|
|
28
|
-
// // valConfig,
|
|
29
|
-
// // "L1_validations",
|
|
30
|
-
// // false,
|
|
31
|
-
// // "./alpha/typescript/"
|
|
32
|
-
// // );
|
|
33
|
-
// // JavaScript generation example
|
|
34
|
-
// const compilerJs = new ConfigCompiler(SupportedLanguages.Javascript);
|
|
35
|
-
// await compilerJs.initialize(buildYaml);
|
|
36
|
-
// await compilerJs.generateCode(
|
|
37
|
-
// valConfig,
|
|
38
|
-
// "L1_validations",
|
|
39
|
-
// false,
|
|
40
|
-
// "./alpha/javascriptNative/"
|
|
41
|
-
// );
|
|
42
|
-
// };
|
|
43
|
-
// (async () => {
|
|
44
|
-
// await main();
|
|
45
|
-
// console.log("========== Code generation completed. ==========");
|
|
46
|
-
// })();
|
package/dist/test.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/test.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { readFileSync } from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { fileURLToPath } from "url";
|
|
4
|
+
import { ConfigCompiler } from "./generator/config-compiler.js";
|
|
5
|
+
import { SupportedLanguages } from "./types/compiler-types.js";
|
|
6
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
7
|
+
const __dirname = path.dirname(__filename);
|
|
8
|
+
const main = async () => {
|
|
9
|
+
const compiler = new ConfigCompiler(SupportedLanguages.Python);
|
|
10
|
+
const buildPath = path.resolve(__dirname, "../samples/build.yaml");
|
|
11
|
+
const valConfigPath = path.resolve(__dirname, "../samples/validation-config.json");
|
|
12
|
+
const buildYaml = readFileSync(buildPath, "utf-8");
|
|
13
|
+
const valConfig = JSON.parse(readFileSync(valConfigPath, "utf-8"));
|
|
14
|
+
await compiler.initialize(buildYaml);
|
|
15
|
+
await compiler.generateCode(valConfig, "L1_validations", false, "./alpha/python/");
|
|
16
|
+
const compilerTy = new ConfigCompiler(SupportedLanguages.Typescript);
|
|
17
|
+
await compilerTy.initialize(buildYaml);
|
|
18
|
+
await compilerTy.generateCode(valConfig, "L1_validations", false, "./alpha/typescript/");
|
|
19
|
+
// JavaScript generation example
|
|
20
|
+
// const compilerJs = new ConfigCompiler(SupportedLanguages.Javascript);
|
|
21
|
+
// await compilerJs.initialize(buildYaml);
|
|
22
|
+
// await compilerJs.generateCode(
|
|
23
|
+
// valConfig,
|
|
24
|
+
// "L1_validations",
|
|
25
|
+
// false,
|
|
26
|
+
// "./alpha/javascriptNative/"
|
|
27
|
+
// );
|
|
28
|
+
};
|
|
29
|
+
(async () => {
|
|
30
|
+
await main();
|
|
31
|
+
console.log("========== Code generation completed. ==========");
|
|
32
|
+
})();
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { ConfigSyntax } from "../../constants/syntax.js";
|
|
2
|
+
import { ValidationConfig } from "../../types/config-types.js";
|
|
3
|
+
export declare function collectLoadData(tests: ValidationConfig[ConfigSyntax.Tests], relevantSessionData: Record<string, Record<string, string>>): void;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { ExternalDataSyntax, TestObjectSyntax, } from "../../constants/syntax.js";
|
|
2
|
+
import { getVariablesFromTest } from "../general-utils/test-object-utils.js";
|
|
3
|
+
export function collectLoadData(tests, relevantSessionData) {
|
|
4
|
+
function processTestObjects(api, testObjects) {
|
|
5
|
+
for (const testObject of testObjects) {
|
|
6
|
+
// Extract variables from this testObject
|
|
7
|
+
const varNames = getVariablesFromTest(testObject);
|
|
8
|
+
for (const varName of varNames) {
|
|
9
|
+
if (typeof testObject[varName] === "string") {
|
|
10
|
+
const path = testObject[varName];
|
|
11
|
+
if (path.includes("_SELF"))
|
|
12
|
+
continue;
|
|
13
|
+
if (path.startsWith(`$.${ExternalDataSyntax}`)) {
|
|
14
|
+
relevantSessionData[api] = relevantSessionData[api] || {};
|
|
15
|
+
// avoid duplication
|
|
16
|
+
const value = path.split(`$.${ExternalDataSyntax}.`)[1];
|
|
17
|
+
if (relevantSessionData[api][varName] !== value) {
|
|
18
|
+
relevantSessionData[api][varName] = value;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
// Recursively process return objects (if array)
|
|
24
|
+
const returnObj = testObject[TestObjectSyntax.Return];
|
|
25
|
+
if (Array.isArray(returnObj)) {
|
|
26
|
+
processTestObjects(api, returnObj);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
for (const api in tests) {
|
|
31
|
+
processTestObjects(api, tests[api]);
|
|
32
|
+
}
|
|
33
|
+
}
|
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ondc-code-generator",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "generate code from build.yaml ",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"build": "tsc && copyfiles -u 1 \"src/**/*.{yaml,html,css,mustache}\" dist/",
|
|
9
9
|
"start": "node ./dist/index.js",
|
|
10
|
+
"test": "npm run build && node ./dist/test.js",
|
|
10
11
|
"dev": "nodemon",
|
|
11
12
|
"clean": "rm -rf dist",
|
|
12
13
|
"prepare": "npm run clean && npm run build"
|
package/dist/example.d.ts
DELETED
|
File without changes
|
package/dist/example.js
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
// import { readFileSync } from "fs";
|
|
3
|
-
// import path from "path";
|
|
4
|
-
// import { fileURLToPath } from "url";
|
|
5
|
-
// const __filename = fileURLToPath(import.meta.url);
|
|
6
|
-
// const __dirname = path.dirname(__filename);
|
|
7
|
-
// import { SupportedLanguages } from "./types/compiler-types.js";
|
|
8
|
-
// const main = async () => {
|
|
9
|
-
// const compiler = new ConfigCompiler(SupportedLanguages.Typescript);
|
|
10
|
-
// const buildPath = path.resolve(__dirname, "../samples/build.yaml");
|
|
11
|
-
// const valConfigPath = path.resolve(
|
|
12
|
-
// __dirname,
|
|
13
|
-
// "../samples/validation-config.json"
|
|
14
|
-
// );
|
|
15
|
-
// const buildYaml = readFileSync(buildPath, "utf-8");
|
|
16
|
-
// const valConfig = JSON.parse(readFileSync(valConfigPath, "utf-8"));
|
|
17
|
-
// await compiler.initialize(buildYaml);
|
|
18
|
-
// await compiler.generateCode(valConfig);
|
|
19
|
-
// };
|
|
20
|
-
// (async () => {
|
|
21
|
-
// await main();
|
|
22
|
-
// })();
|