vovk 3.0.0-draft.99 → 3.0.2
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/README.md +23 -12
- package/bin/index.mjs +10 -0
- package/dist/client/createRPC.d.ts +13 -3
- package/dist/client/createRPC.js +112 -50
- package/dist/client/defaultHandler.d.ts +5 -1
- package/dist/client/defaultHandler.js +12 -9
- package/dist/client/defaultStreamHandler.d.ts +16 -4
- package/dist/client/defaultStreamHandler.js +262 -62
- package/dist/client/fetcher.d.ts +41 -3
- package/dist/client/fetcher.js +125 -60
- package/dist/client/progressive.d.ts +15 -0
- package/dist/client/progressive.js +56 -0
- package/dist/{utils → client}/serializeQuery.d.ts +2 -2
- package/dist/{utils → client}/serializeQuery.js +2 -5
- package/dist/core/HttpException.d.ts +16 -0
- package/dist/core/HttpException.js +26 -0
- package/dist/core/JSONLinesResponder.d.ts +42 -0
- package/dist/core/JSONLinesResponder.js +94 -0
- package/dist/core/controllersToStaticParams.d.ts +13 -0
- package/dist/core/controllersToStaticParams.js +32 -0
- package/dist/core/createDecorator.d.ts +18 -0
- package/dist/core/createDecorator.js +60 -0
- package/dist/core/decorators.d.ts +66 -0
- package/dist/core/decorators.js +155 -0
- package/dist/core/getSchema.d.ts +21 -0
- package/dist/core/getSchema.js +31 -0
- package/dist/core/initSegment.d.ts +33 -0
- package/dist/core/initSegment.js +35 -0
- package/dist/core/multitenant.d.ts +33 -0
- package/dist/core/multitenant.js +132 -0
- package/dist/core/resolveGeneratorConfigValues.d.ts +19 -0
- package/dist/core/resolveGeneratorConfigValues.js +59 -0
- package/dist/{utils → core}/setHandlerSchema.d.ts +2 -2
- package/dist/{utils → core}/setHandlerSchema.js +1 -4
- package/dist/core/toDownloadResponse.d.ts +11 -0
- package/dist/core/toDownloadResponse.js +25 -0
- package/dist/core/vovkApp.d.ts +36 -0
- package/dist/core/vovkApp.js +318 -0
- package/dist/index.d.ts +25 -59
- package/dist/index.js +23 -23
- package/dist/internal.d.ts +17 -0
- package/dist/internal.js +10 -0
- package/dist/openapi/error.d.ts +6 -0
- package/dist/openapi/error.js +97 -0
- package/dist/openapi/openAPIToVovkSchema/applyComponentsSchemas.d.ts +3 -0
- package/dist/openapi/openAPIToVovkSchema/applyComponentsSchemas.js +65 -0
- package/dist/openapi/openAPIToVovkSchema/index.d.ts +5 -0
- package/dist/openapi/openAPIToVovkSchema/index.js +153 -0
- package/dist/openapi/openAPIToVovkSchema/inlineRefs.d.ts +9 -0
- package/dist/openapi/openAPIToVovkSchema/inlineRefs.js +99 -0
- package/dist/openapi/operation.d.ts +26 -0
- package/dist/openapi/operation.js +19 -0
- package/dist/openapi/tool.d.ts +6 -0
- package/dist/openapi/tool.js +12 -0
- package/dist/openapi/vovkSchemaToOpenAPI.d.ts +21 -0
- package/dist/openapi/vovkSchemaToOpenAPI.js +250 -0
- package/dist/req/bufferBody.d.ts +1 -0
- package/dist/req/bufferBody.js +30 -0
- package/dist/req/parseBody.d.ts +4 -0
- package/dist/req/parseBody.js +49 -0
- package/dist/req/parseForm.d.ts +1 -0
- package/dist/req/parseForm.js +24 -0
- package/dist/{utils → req}/parseQuery.d.ts +1 -2
- package/dist/{utils → req}/parseQuery.js +12 -12
- package/dist/req/reqMeta.d.ts +2 -0
- package/dist/{utils → req}/reqMeta.js +1 -4
- package/dist/req/reqQuery.d.ts +2 -0
- package/dist/req/reqQuery.js +4 -0
- package/dist/req/validateContentType.d.ts +1 -0
- package/dist/req/validateContentType.js +32 -0
- package/dist/samples/createCodeSamples.d.ts +20 -0
- package/dist/samples/createCodeSamples.js +293 -0
- package/dist/samples/objectToCode.d.ts +8 -0
- package/dist/samples/objectToCode.js +38 -0
- package/dist/samples/schemaToCode.d.ts +11 -0
- package/dist/samples/schemaToCode.js +264 -0
- package/dist/samples/schemaToObject.d.ts +2 -0
- package/dist/samples/schemaToObject.js +164 -0
- package/dist/samples/schemaToTsType.d.ts +2 -0
- package/dist/samples/schemaToTsType.js +114 -0
- package/dist/tools/ToModelOutput.d.ts +8 -0
- package/dist/tools/ToModelOutput.js +10 -0
- package/dist/tools/createTool.d.ts +126 -0
- package/dist/tools/createTool.js +6 -0
- package/dist/tools/createToolFactory.d.ts +135 -0
- package/dist/tools/createToolFactory.js +62 -0
- package/dist/tools/deriveTools.d.ts +46 -0
- package/dist/tools/deriveTools.js +132 -0
- package/dist/tools/toModelOutputDefault.d.ts +7 -0
- package/dist/tools/toModelOutputDefault.js +7 -0
- package/dist/tools/toModelOutputMCP.d.ts +30 -0
- package/dist/tools/toModelOutputMCP.js +54 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/types/client.d.ts +139 -0
- package/dist/types/client.js +1 -0
- package/dist/types/config.d.ts +151 -0
- package/dist/types/config.js +1 -0
- package/dist/types/core.d.ts +115 -0
- package/dist/types/core.js +1 -0
- package/dist/types/enums.d.ts +75 -0
- package/dist/{types.js → types/enums.js} +21 -9
- package/dist/types/inference.d.ts +117 -0
- package/dist/types/inference.js +1 -0
- package/dist/types/json-schema.d.ts +51 -0
- package/dist/types/json-schema.js +1 -0
- package/dist/types/operation.d.ts +5 -0
- package/dist/types/operation.js +1 -0
- package/dist/types/package.d.ts +544 -0
- package/dist/types/package.js +5 -0
- package/dist/types/request.d.ts +48 -0
- package/dist/types/request.js +1 -0
- package/dist/types/standard-schema.d.ts +117 -0
- package/dist/types/standard-schema.js +6 -0
- package/dist/types/tools.d.ts +43 -0
- package/dist/types/tools.js +1 -0
- package/dist/types/utils.d.ts +9 -0
- package/dist/types/utils.js +1 -0
- package/dist/types/validation.d.ts +48 -0
- package/dist/types/validation.js +1 -0
- package/dist/utils/camelCase.d.ts +6 -0
- package/dist/utils/camelCase.js +34 -0
- package/dist/utils/deepExtend.d.ts +54 -0
- package/dist/utils/deepExtend.js +127 -0
- package/dist/utils/fileNameToDisposition.d.ts +1 -0
- package/dist/utils/fileNameToDisposition.js +3 -0
- package/dist/utils/shim.d.ts +1 -0
- package/dist/utils/shim.js +1 -1
- package/dist/utils/toKebabCase.d.ts +1 -0
- package/dist/utils/toKebabCase.js +5 -0
- package/dist/utils/trimPath.d.ts +1 -0
- package/dist/utils/trimPath.js +1 -0
- package/dist/utils/upperFirst.d.ts +1 -0
- package/dist/utils/upperFirst.js +3 -0
- package/dist/validation/createStandardValidation.d.ts +268 -0
- package/dist/validation/createStandardValidation.js +45 -0
- package/dist/validation/createValidateOnClient.d.ts +14 -0
- package/dist/validation/createValidateOnClient.js +23 -0
- package/dist/validation/procedure.d.ts +261 -0
- package/dist/validation/procedure.js +8 -0
- package/dist/validation/withValidationLibrary.d.ts +119 -0
- package/dist/validation/withValidationLibrary.js +174 -0
- package/package.json +45 -11
- package/dist/HttpException.d.ts +0 -7
- package/dist/HttpException.js +0 -15
- package/dist/StreamJSONResponse.d.ts +0 -14
- package/dist/StreamJSONResponse.js +0 -57
- package/dist/VovkApp.d.ts +0 -29
- package/dist/VovkApp.js +0 -188
- package/dist/client/index.d.ts +0 -3
- package/dist/client/index.js +0 -7
- package/dist/client/types.d.ts +0 -104
- package/dist/client/types.js +0 -2
- package/dist/createDecorator.d.ts +0 -6
- package/dist/createDecorator.js +0 -43
- package/dist/createVovkApp.d.ts +0 -62
- package/dist/createVovkApp.js +0 -118
- package/dist/types.d.ts +0 -220
- package/dist/utils/generateStaticAPI.d.ts +0 -4
- package/dist/utils/generateStaticAPI.js +0 -18
- package/dist/utils/getSchema.d.ts +0 -20
- package/dist/utils/getSchema.js +0 -33
- package/dist/utils/reqForm.d.ts +0 -2
- package/dist/utils/reqForm.js +0 -13
- package/dist/utils/reqMeta.d.ts +0 -2
- package/dist/utils/reqQuery.d.ts +0 -2
- package/dist/utils/reqQuery.js +0 -10
- package/dist/utils/withValidation.d.ts +0 -20
- package/dist/utils/withValidation.js +0 -72
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
import { schemaToCode, getSampleValue } from './schemaToCode.js';
|
|
2
|
+
import { objectToCode } from './objectToCode.js';
|
|
3
|
+
const toSnakeCase = (str) => str
|
|
4
|
+
.replace(/-/g, '_') // Replace hyphens with underscores
|
|
5
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1_$2') // Add underscore between lowercase/digit and uppercase
|
|
6
|
+
.replace(/([A-Z])([A-Z])(?=[a-z])/g, '$1_$2') // Add underscore between uppercase letters if the second one is followed by a lowercase
|
|
7
|
+
.toLowerCase()
|
|
8
|
+
.replace(/^_/, ''); // Remove leading underscore
|
|
9
|
+
const getIndentSpaces = (level) => ' '.repeat(level);
|
|
10
|
+
function isTextFormat(mimeType) {
|
|
11
|
+
if (!mimeType)
|
|
12
|
+
return false;
|
|
13
|
+
return (mimeType.startsWith('text/') ||
|
|
14
|
+
[
|
|
15
|
+
'application/json',
|
|
16
|
+
'application/ld+json',
|
|
17
|
+
'application/xml',
|
|
18
|
+
'application/xhtml+xml',
|
|
19
|
+
'application/javascript',
|
|
20
|
+
'application/typescript',
|
|
21
|
+
'application/yaml',
|
|
22
|
+
'application/x-yaml',
|
|
23
|
+
'application/toml',
|
|
24
|
+
'application/sql',
|
|
25
|
+
'application/graphql',
|
|
26
|
+
'application/x-www-form-urlencoded',
|
|
27
|
+
].includes(mimeType) ||
|
|
28
|
+
mimeType.endsWith('+json') ||
|
|
29
|
+
mimeType.endsWith('+xml'));
|
|
30
|
+
}
|
|
31
|
+
const isForm = (schema) => {
|
|
32
|
+
const contentTypes = schema['x-contentType'] ?? [];
|
|
33
|
+
return contentTypes.some((ct) => ct === 'multipart/form-data' || ct === 'application/x-www-form-urlencoded') ?? false;
|
|
34
|
+
};
|
|
35
|
+
function generateTypeScriptCode({ handlerName, rpcName, packageName, queryValidation, bodyValidation, paramsValidation, outputValidation, iterationValidation, hasArg, config, }) {
|
|
36
|
+
const getTsSample = (schema, indent) => schemaToCode(schema, { stripQuotes: true, indent: indent ?? 4 });
|
|
37
|
+
const getTsFormSample = (schema) => {
|
|
38
|
+
let formSample = '\nconst formData = new FormData();';
|
|
39
|
+
for (const [key, prop] of Object.entries(schema.properties || {})) {
|
|
40
|
+
const target = prop.oneOf?.[0] || prop.anyOf?.[0] || prop.allOf?.[0] || prop;
|
|
41
|
+
const desc = target.description ?? prop.description ?? undefined;
|
|
42
|
+
if (target.type === 'array' && target.items && typeof target.items !== 'boolean') {
|
|
43
|
+
formSample += getTsFormAppend(target.items, key, desc);
|
|
44
|
+
formSample += getTsFormAppend(target.items, key, desc);
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
formSample += getTsFormAppend(target, key, desc);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return formSample;
|
|
51
|
+
};
|
|
52
|
+
const getTsFormAppend = (schema, key, description) => {
|
|
53
|
+
let sampleValue;
|
|
54
|
+
if (schema.type === 'string' && schema.format === 'binary') {
|
|
55
|
+
sampleValue = `new Blob(${isTextFormat(schema.contentMediaType) ? '["text_content"]' : '[binary_data]'}${schema.contentMediaType ? `, { type: "${schema.contentMediaType}" }` : ''})`;
|
|
56
|
+
}
|
|
57
|
+
else if (schema.type === 'object') {
|
|
58
|
+
sampleValue = '"object_unknown"';
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
sampleValue = `"${getSampleValue(schema)}"`;
|
|
62
|
+
}
|
|
63
|
+
const desc = schema.description ?? description;
|
|
64
|
+
return `\n${desc ? `// ${desc}\n` : ''}formData.append("${key}", ${sampleValue});`;
|
|
65
|
+
};
|
|
66
|
+
const tsArgs = hasArg
|
|
67
|
+
? `{
|
|
68
|
+
${[
|
|
69
|
+
bodyValidation ? ` body: ${isForm(bodyValidation) ? 'formData' : getTsSample(bodyValidation)},` : null,
|
|
70
|
+
queryValidation ? ` query: ${getTsSample(queryValidation)},` : null,
|
|
71
|
+
paramsValidation ? ` params: ${getTsSample(paramsValidation)},` : null,
|
|
72
|
+
config?.apiRoot ? ` apiRoot: '${config.apiRoot}',` : null,
|
|
73
|
+
config?.headers
|
|
74
|
+
? ` init: {
|
|
75
|
+
headers: ${objectToCode(config.headers, { stripQuotes: true, indent: 6, nestingIndent: 4 })}
|
|
76
|
+
},`
|
|
77
|
+
: null,
|
|
78
|
+
]
|
|
79
|
+
.filter(Boolean)
|
|
80
|
+
.join('\n')}
|
|
81
|
+
}`
|
|
82
|
+
: '';
|
|
83
|
+
const TS_CODE = `import { ${rpcName} } from '${packageName}';
|
|
84
|
+
${bodyValidation && isForm(bodyValidation) ? `${getTsFormSample(bodyValidation)}\n` : ''}
|
|
85
|
+
${iterationValidation ? 'using' : 'const'} response = await ${rpcName}.${handlerName}(${tsArgs});
|
|
86
|
+
${outputValidation
|
|
87
|
+
? `
|
|
88
|
+
console.log(response);
|
|
89
|
+
/*
|
|
90
|
+
${getTsSample(outputValidation, 0)}
|
|
91
|
+
*/`
|
|
92
|
+
: ''}${iterationValidation
|
|
93
|
+
? `
|
|
94
|
+
for await (const item of response) {
|
|
95
|
+
console.log(item);
|
|
96
|
+
/*
|
|
97
|
+
${getTsSample(iterationValidation)}
|
|
98
|
+
*/
|
|
99
|
+
}`
|
|
100
|
+
: ''}`;
|
|
101
|
+
return TS_CODE.trim();
|
|
102
|
+
}
|
|
103
|
+
function generatePythonCode({ handlerName, rpcName, packageName, queryValidation, bodyValidation, paramsValidation, outputValidation, iterationValidation, hasArg, config, }) {
|
|
104
|
+
const getPySample = (schema, indent) => schemaToCode(schema, {
|
|
105
|
+
stripQuotes: false,
|
|
106
|
+
indent: indent ?? 4,
|
|
107
|
+
comment: '#',
|
|
108
|
+
ignoreBinary: true,
|
|
109
|
+
nestingIndent: 4,
|
|
110
|
+
});
|
|
111
|
+
const handlerNameSnake = toSnakeCase(handlerName);
|
|
112
|
+
const getFileTouple = (schema) => {
|
|
113
|
+
return `('name.ext', BytesIO(${isTextFormat(schema.contentMediaType) ? '"text_content".encode("utf-8")' : 'binary_data'})${schema.contentMediaType ? `, "${schema.contentMediaType}"` : ''})`;
|
|
114
|
+
};
|
|
115
|
+
const getPyFiles = (schema) => {
|
|
116
|
+
return Object.entries(schema.properties ?? {}).reduce((acc, [key, prop]) => {
|
|
117
|
+
const target = prop.oneOf?.[0] || prop.anyOf?.[0] || prop.allOf?.[0] || prop;
|
|
118
|
+
const desc = target.description ?? prop.description ?? undefined;
|
|
119
|
+
if (target.type === 'string' && target.format === 'binary') {
|
|
120
|
+
acc.push(`${desc ? `${getIndentSpaces(8)}# ${desc}\n` : ''}${getIndentSpaces(8)}('${key}', ${getFileTouple(target)})`);
|
|
121
|
+
}
|
|
122
|
+
else if (target.type === 'array' &&
|
|
123
|
+
target.items &&
|
|
124
|
+
typeof target.items !== 'boolean' &&
|
|
125
|
+
target.items.format === 'binary') {
|
|
126
|
+
const val = `${desc ? `${getIndentSpaces(8)}# ${desc}\n` : ''}${getIndentSpaces(8)}('${key}', ${getFileTouple(target.items)})`;
|
|
127
|
+
acc.push(val, val);
|
|
128
|
+
}
|
|
129
|
+
return acc;
|
|
130
|
+
}, []);
|
|
131
|
+
};
|
|
132
|
+
const pyFiles = bodyValidation ? getPyFiles(bodyValidation) : null;
|
|
133
|
+
const pyFilesArg = pyFiles?.length
|
|
134
|
+
? `${getIndentSpaces(4)}files=[\n${pyFiles.join(',\n')}\n${getIndentSpaces(4)}],`
|
|
135
|
+
: null;
|
|
136
|
+
const PY_CODE = `from ${packageName} import ${rpcName}
|
|
137
|
+
${bodyValidation && isForm(bodyValidation) ? 'from io import BytesIO\n' : ''}
|
|
138
|
+
response = ${rpcName}.${handlerNameSnake}(${hasArg
|
|
139
|
+
? '\n' +
|
|
140
|
+
[
|
|
141
|
+
bodyValidation ? ` body=${getPySample(bodyValidation)},` : null,
|
|
142
|
+
pyFilesArg,
|
|
143
|
+
queryValidation ? ` query=${getPySample(queryValidation)},` : null,
|
|
144
|
+
paramsValidation ? ` params=${getPySample(paramsValidation)},` : null,
|
|
145
|
+
config?.apiRoot ? ` api_root="${config.apiRoot}",` : null,
|
|
146
|
+
config?.headers
|
|
147
|
+
? ` headers=${objectToCode(config.headers, { stripQuotes: false, indent: 4, nestingIndent: 4 })},`
|
|
148
|
+
: null,
|
|
149
|
+
]
|
|
150
|
+
.filter(Boolean)
|
|
151
|
+
.join('\n') +
|
|
152
|
+
'\n'
|
|
153
|
+
: ''})
|
|
154
|
+
|
|
155
|
+
${outputValidation ? `print(response)\n${getPySample(outputValidation, 0)}` : ''}${iterationValidation
|
|
156
|
+
? `for i, item in enumerate(response):
|
|
157
|
+
print(f"iteration #{i}:\\n {item}")
|
|
158
|
+
# iteration #0:
|
|
159
|
+
${getPySample(iterationValidation)}`
|
|
160
|
+
: ''}`;
|
|
161
|
+
return PY_CODE.trim();
|
|
162
|
+
}
|
|
163
|
+
function generateRustCode({ handlerName, rpcName, packageName, queryValidation, bodyValidation, paramsValidation, outputValidation, iterationValidation, config, }) {
|
|
164
|
+
const getRsJSONSample = (schema, indent) => schemaToCode(schema, { stripQuotes: false, indent: indent ?? 4 });
|
|
165
|
+
const getRsOutputSample = (schema, indent) => schemaToCode(schema, { stripQuotes: true, indent: indent ?? 4 });
|
|
166
|
+
const getRsFormSample = (schema) => {
|
|
167
|
+
let formSample = 'let form = reqwest::multipart::Form::new()';
|
|
168
|
+
for (const [key, prop] of Object.entries(schema.properties || {})) {
|
|
169
|
+
const target = prop.oneOf?.[0] || prop.anyOf?.[0] || prop.allOf?.[0] || prop;
|
|
170
|
+
const desc = target.description ?? prop.description ?? undefined;
|
|
171
|
+
if (target.type === 'array' && target.items && typeof target.items !== 'boolean') {
|
|
172
|
+
formSample += getRsFormPart(target.items, key, desc);
|
|
173
|
+
formSample += getRsFormPart(target.items, key, desc);
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
formSample += getRsFormPart(target, key, desc);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return formSample;
|
|
180
|
+
};
|
|
181
|
+
const getRsFormPart = (schema, key, description) => {
|
|
182
|
+
let sampleValue;
|
|
183
|
+
if (schema.type === 'string' && schema.format === 'binary') {
|
|
184
|
+
sampleValue = isTextFormat(schema.contentMediaType)
|
|
185
|
+
? 'reqwest::multipart::Part::text("text_content")'
|
|
186
|
+
: 'reqwest::multipart::Part::bytes(binary_data)';
|
|
187
|
+
if (schema.contentMediaType) {
|
|
188
|
+
sampleValue += `.mime_str("${schema.contentMediaType}").unwrap()`;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
else if (schema.type === 'object') {
|
|
192
|
+
sampleValue = '"object_unknown"';
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
sampleValue = `"${getSampleValue(schema)}"`;
|
|
196
|
+
}
|
|
197
|
+
const desc = schema.description ?? description;
|
|
198
|
+
return `\n${getIndentSpaces(4)}${desc ? `// ${desc}\n` : ''}${getIndentSpaces(4)}.part("${key}", ${sampleValue});`;
|
|
199
|
+
};
|
|
200
|
+
const getHashMapSample = (map, indent = 4) => {
|
|
201
|
+
const entries = Object.entries(map)
|
|
202
|
+
.map(([key, value]) => {
|
|
203
|
+
return `${getIndentSpaces(indent + 2)}("${key}".to_string(), "${value}".to_string())`;
|
|
204
|
+
})
|
|
205
|
+
.join(',\n');
|
|
206
|
+
return `Some(&HashMap::from([\n${entries}\n${getIndentSpaces(4)}]))`;
|
|
207
|
+
};
|
|
208
|
+
const getBody = (schema) => {
|
|
209
|
+
if (isForm(schema)) {
|
|
210
|
+
return 'form';
|
|
211
|
+
}
|
|
212
|
+
return serdeUnwrap(getRsJSONSample(schema));
|
|
213
|
+
};
|
|
214
|
+
const handlerNameSnake = toSnakeCase(handlerName);
|
|
215
|
+
const rpcNameSnake = toSnakeCase(rpcName);
|
|
216
|
+
const serdeUnwrap = (fake) => `from_value(json!(${fake})).unwrap()`;
|
|
217
|
+
const RS_CODE = `use ${packageName}::${rpcNameSnake};
|
|
218
|
+
use serde_json::{
|
|
219
|
+
from_value,
|
|
220
|
+
json
|
|
221
|
+
};
|
|
222
|
+
${iterationValidation ? 'use futures_util::StreamExt;\n' : ''}${bodyValidation && isForm(bodyValidation) ? `use reqwest::multipart;\n` : ''}#[tokio::main]
|
|
223
|
+
async fn main() {${bodyValidation && isForm(bodyValidation) ? `\n ${getRsFormSample(bodyValidation)}\n` : ''}
|
|
224
|
+
let response = ${rpcNameSnake}::${handlerNameSnake}(
|
|
225
|
+
${bodyValidation ? getBody(bodyValidation) : '()'}, /* body */
|
|
226
|
+
${queryValidation ? serdeUnwrap(getRsJSONSample(queryValidation)) : '()'}, /* query */
|
|
227
|
+
${paramsValidation ? serdeUnwrap(getRsJSONSample(paramsValidation)) : '()'}, /* params */
|
|
228
|
+
${config?.headers ? `${getHashMapSample(config.headers)}, /* headers */` : 'None, /* headers (HashMap) */ '}
|
|
229
|
+
${config?.apiRoot ? `Some("${config.apiRoot}"), /* api_root */` : 'None, /* api_root */'}
|
|
230
|
+
false, /* disable_client_validation */
|
|
231
|
+
).await;${outputValidation
|
|
232
|
+
? `\n\nmatch response {
|
|
233
|
+
Ok(output) => println!("{:?}", output),
|
|
234
|
+
/*
|
|
235
|
+
output ${getRsOutputSample(outputValidation)}
|
|
236
|
+
*/
|
|
237
|
+
Err(e) => println!("error: {:?}", e),
|
|
238
|
+
}`
|
|
239
|
+
: ''}${iterationValidation
|
|
240
|
+
? `\n\nmatch response {
|
|
241
|
+
Ok(mut stream) => {
|
|
242
|
+
let mut i = 0;
|
|
243
|
+
while let Some(item) = stream.next().await {
|
|
244
|
+
match item {
|
|
245
|
+
Ok(value) => {
|
|
246
|
+
println!("#{}: {:?}", i, value);
|
|
247
|
+
/*
|
|
248
|
+
#0: iteration ${getRsOutputSample(iterationValidation, 8)}
|
|
249
|
+
*/
|
|
250
|
+
i += 1;
|
|
251
|
+
}
|
|
252
|
+
Err(e) => {
|
|
253
|
+
eprintln!("stream error: {:?}", e);
|
|
254
|
+
break;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
},
|
|
259
|
+
Err(e) => println!("Error initiating stream: {:?}", e),
|
|
260
|
+
}`
|
|
261
|
+
: ''}
|
|
262
|
+
}`;
|
|
263
|
+
return RS_CODE.trim();
|
|
264
|
+
}
|
|
265
|
+
export function createCodeSamples({ handlerName, handlerSchema, controllerSchema, package: packageJson, config, }) {
|
|
266
|
+
const queryValidation = handlerSchema?.validation?.query;
|
|
267
|
+
const bodyValidation = handlerSchema?.validation?.body;
|
|
268
|
+
const paramsValidation = handlerSchema?.validation?.params;
|
|
269
|
+
const outputValidation = handlerSchema?.validation?.output;
|
|
270
|
+
const iterationValidation = handlerSchema?.validation?.iteration;
|
|
271
|
+
const hasArg = !!queryValidation || !!bodyValidation || !!paramsValidation || !!config?.apiRoot || !!config?.headers;
|
|
272
|
+
const rpcName = controllerSchema.rpcModuleName;
|
|
273
|
+
const packageName = packageJson?.name || 'vovk-client';
|
|
274
|
+
const packageNameSnake = toSnakeCase(packageName);
|
|
275
|
+
const pyPackageName = packageJson?.py_name ?? packageNameSnake;
|
|
276
|
+
const rsPackageName = packageJson?.rs_name ?? packageNameSnake;
|
|
277
|
+
const commonParams = {
|
|
278
|
+
handlerName,
|
|
279
|
+
rpcName,
|
|
280
|
+
packageName,
|
|
281
|
+
queryValidation,
|
|
282
|
+
bodyValidation,
|
|
283
|
+
paramsValidation,
|
|
284
|
+
outputValidation,
|
|
285
|
+
iterationValidation,
|
|
286
|
+
hasArg,
|
|
287
|
+
config,
|
|
288
|
+
};
|
|
289
|
+
const ts = generateTypeScriptCode(commonParams);
|
|
290
|
+
const py = generatePythonCode({ ...commonParams, packageName: pyPackageName });
|
|
291
|
+
const rs = generateRustCode({ ...commonParams, packageName: rsPackageName });
|
|
292
|
+
return { ts, py, rs };
|
|
293
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export function objectToCode(obj, options) {
|
|
2
|
+
const { stripQuotes = false, indent = 0, nestingIndent = 2, quote = '"' } = options || {};
|
|
3
|
+
// Use JSON.stringify with the nesting indent
|
|
4
|
+
let result = JSON.stringify(obj, null, nestingIndent);
|
|
5
|
+
// Replace double quotes with single quotes for string values if requested
|
|
6
|
+
if (quote === "'") {
|
|
7
|
+
// First, escape any existing single quotes in string values
|
|
8
|
+
result = result.replace(/"([^"]*)"/g, (match, content) => {
|
|
9
|
+
// Check if this is a key (followed by colon) or a value
|
|
10
|
+
const matchIndex = result.indexOf(match);
|
|
11
|
+
const afterMatch = result.substring(matchIndex + match.length);
|
|
12
|
+
if (afterMatch.startsWith(':')) {
|
|
13
|
+
// This is a key, keep it for now
|
|
14
|
+
return match;
|
|
15
|
+
}
|
|
16
|
+
// This is a value, replace quotes and escape single quotes
|
|
17
|
+
const escaped = content.replace(/'/g, "\\'");
|
|
18
|
+
return `'${escaped}'`;
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
// Strip quotes from keys if requested
|
|
22
|
+
if (stripQuotes) {
|
|
23
|
+
// Remove quotes from keys that are valid JavaScript identifiers
|
|
24
|
+
// Keep quotes for keys with special characters (like 'x-foo', spaces, etc.)
|
|
25
|
+
const keyQuote = quote === "'" ? "'" : '"';
|
|
26
|
+
const pattern = new RegExp(`${keyQuote}([a-zA-Z_$][a-zA-Z0-9_$]*)${keyQuote}:`, 'g');
|
|
27
|
+
result = result.replace(pattern, '$1:');
|
|
28
|
+
}
|
|
29
|
+
// Apply base indentation if specified
|
|
30
|
+
if (indent > 0) {
|
|
31
|
+
const indentStr = ' '.repeat(indent);
|
|
32
|
+
result = result
|
|
33
|
+
.split('\n')
|
|
34
|
+
.map((line, i) => (i === 0 ? line : indentStr + line))
|
|
35
|
+
.join('\n');
|
|
36
|
+
}
|
|
37
|
+
return result;
|
|
38
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { VovkJSONSchemaBase } from '../types/json-schema.js';
|
|
2
|
+
interface SamplerOptions {
|
|
3
|
+
comment?: '//' | '#';
|
|
4
|
+
stripQuotes?: boolean;
|
|
5
|
+
indent?: number;
|
|
6
|
+
nestingIndent?: number;
|
|
7
|
+
ignoreBinary?: boolean;
|
|
8
|
+
}
|
|
9
|
+
export declare function schemaToCode(schema: VovkJSONSchemaBase, options: SamplerOptions, rootSchema?: VovkJSONSchemaBase): string;
|
|
10
|
+
export declare function getSampleValue(schema: VovkJSONSchemaBase, rootSchema?: VovkJSONSchemaBase, ignoreBinary?: boolean): unknown;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
export function schemaToCode(schema, options, rootSchema) {
|
|
2
|
+
const { comment = '//', stripQuotes = false, indent = 0, nestingIndent = 4, ignoreBinary = false } = options;
|
|
3
|
+
if (!schema || typeof schema !== 'object')
|
|
4
|
+
return 'null';
|
|
5
|
+
// Use the input schema as the root if not provided
|
|
6
|
+
rootSchema = rootSchema || schema;
|
|
7
|
+
// Get the sample value
|
|
8
|
+
const sampleValue = getSampleValue(schema, rootSchema, ignoreBinary);
|
|
9
|
+
// Format the output with descriptions
|
|
10
|
+
return formatWithDescriptions(sampleValue, schema, rootSchema, comment, stripQuotes, indent, nestingIndent, ignoreBinary, true // isTopLevel
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
export function getSampleValue(schema, rootSchema, ignoreBinary) {
|
|
14
|
+
if (!schema || typeof schema !== 'object')
|
|
15
|
+
return null;
|
|
16
|
+
rootSchema = rootSchema || schema;
|
|
17
|
+
// Check if this is a binary string schema and should be ignored
|
|
18
|
+
if (ignoreBinary && schema.type === 'string' && schema.format === 'binary') {
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
// If there's an example, use it
|
|
22
|
+
if (schema.example !== undefined) {
|
|
23
|
+
return schema.example;
|
|
24
|
+
}
|
|
25
|
+
// If there are examples, use one of them
|
|
26
|
+
if (schema.examples && schema.examples.length > 0) {
|
|
27
|
+
return schema.examples[0];
|
|
28
|
+
}
|
|
29
|
+
// Handle const if present
|
|
30
|
+
if (schema.const !== undefined) {
|
|
31
|
+
return schema.const;
|
|
32
|
+
}
|
|
33
|
+
// Handle $ref if present
|
|
34
|
+
if (schema.$ref) {
|
|
35
|
+
return handleRef(schema.$ref, rootSchema, ignoreBinary);
|
|
36
|
+
}
|
|
37
|
+
// Handle enum if present
|
|
38
|
+
if (schema.enum && schema.enum.length > 0) {
|
|
39
|
+
return schema.enum[0];
|
|
40
|
+
}
|
|
41
|
+
// Handle oneOf, anyOf, allOf
|
|
42
|
+
if (schema.oneOf && schema.oneOf.length > 0) {
|
|
43
|
+
return getSampleValue(schema.oneOf[0], rootSchema, ignoreBinary);
|
|
44
|
+
}
|
|
45
|
+
if (schema.anyOf && schema.anyOf.length > 0) {
|
|
46
|
+
return getSampleValue(schema.anyOf[0], rootSchema, ignoreBinary);
|
|
47
|
+
}
|
|
48
|
+
if (schema.allOf && schema.allOf.length > 0) {
|
|
49
|
+
// Merge all schemas in allOf
|
|
50
|
+
const mergedSchema = schema.allOf.reduce((acc, s) => Object.assign(acc, s), {});
|
|
51
|
+
return getSampleValue(mergedSchema, rootSchema, ignoreBinary);
|
|
52
|
+
}
|
|
53
|
+
// Handle different types
|
|
54
|
+
if (schema.type) {
|
|
55
|
+
switch (schema.type) {
|
|
56
|
+
case 'string':
|
|
57
|
+
return handleString(schema);
|
|
58
|
+
case 'number':
|
|
59
|
+
case 'integer':
|
|
60
|
+
return handleNumber(schema);
|
|
61
|
+
case 'boolean':
|
|
62
|
+
return handleBoolean();
|
|
63
|
+
case 'object':
|
|
64
|
+
return handleObject(schema, rootSchema, ignoreBinary);
|
|
65
|
+
case 'array':
|
|
66
|
+
return handleArray(schema, rootSchema, ignoreBinary);
|
|
67
|
+
case 'null':
|
|
68
|
+
return null;
|
|
69
|
+
default:
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
// If type is not specified but properties are, treat it as an object
|
|
74
|
+
if (schema.properties) {
|
|
75
|
+
return handleObject(schema, rootSchema, ignoreBinary);
|
|
76
|
+
}
|
|
77
|
+
// Default fallback
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
function formatWithDescriptions(value, schema, rootSchema, comment, stripQuotes, indent, nestingIndent, ignoreBinary, isTopLevel) {
|
|
81
|
+
const indentStr = ' '.repeat(indent);
|
|
82
|
+
const nestIndentStr = ' '.repeat(nestingIndent); // Create nesting indent string
|
|
83
|
+
// Handle undefined (for ignored binary fields)
|
|
84
|
+
if (value === undefined) {
|
|
85
|
+
return '';
|
|
86
|
+
}
|
|
87
|
+
// Handle null
|
|
88
|
+
if (value === null) {
|
|
89
|
+
return 'null';
|
|
90
|
+
}
|
|
91
|
+
// Handle primitives
|
|
92
|
+
if (typeof value !== 'object' || value instanceof Date) {
|
|
93
|
+
return JSON.stringify(value);
|
|
94
|
+
}
|
|
95
|
+
// Handle arrays
|
|
96
|
+
if (Array.isArray(value)) {
|
|
97
|
+
if (value.length === 0)
|
|
98
|
+
return '[]';
|
|
99
|
+
const items = value.map((item) => {
|
|
100
|
+
const itemSchema = schema.items && typeof schema.items === 'object' ? schema.items : {};
|
|
101
|
+
const formattedItem = formatWithDescriptions(item, itemSchema, rootSchema, comment, stripQuotes, indent + nestingIndent, // Use nestingIndent instead of hardcoded 4
|
|
102
|
+
nestingIndent, ignoreBinary, false);
|
|
103
|
+
return `${indentStr}${nestIndentStr}${formattedItem}`; // Use nestIndentStr for item indentation
|
|
104
|
+
});
|
|
105
|
+
return `[\n${items.join(',\n')}\n${indentStr}]`;
|
|
106
|
+
}
|
|
107
|
+
// Handle objects
|
|
108
|
+
if (typeof value === 'object') {
|
|
109
|
+
const entries = Object.entries(value);
|
|
110
|
+
if (entries.length === 0)
|
|
111
|
+
return '{}';
|
|
112
|
+
const formattedEntries = [];
|
|
113
|
+
// Add top-level description for objects
|
|
114
|
+
if (isTopLevel && schema.type === 'object' && schema.description) {
|
|
115
|
+
const descLines = schema.description.split('\n');
|
|
116
|
+
formattedEntries.push(`${indentStr}${nestIndentStr}${comment} -----`);
|
|
117
|
+
descLines.forEach((line) => {
|
|
118
|
+
formattedEntries.push(`${indentStr}${nestIndentStr}${comment} ${line.trim()}`);
|
|
119
|
+
});
|
|
120
|
+
formattedEntries.push(`${indentStr}${nestIndentStr}${comment} -----`);
|
|
121
|
+
}
|
|
122
|
+
entries.forEach(([key, val], index) => {
|
|
123
|
+
const propSchema = schema.properties?.[key] ?? {};
|
|
124
|
+
// Handle $ref in property schema
|
|
125
|
+
let resolvedPropSchema = propSchema;
|
|
126
|
+
if (propSchema.$ref) {
|
|
127
|
+
resolvedPropSchema = resolveRef(propSchema.$ref, rootSchema);
|
|
128
|
+
}
|
|
129
|
+
// Add property description if it exists
|
|
130
|
+
if (resolvedPropSchema.description) {
|
|
131
|
+
const descLines = resolvedPropSchema.description.split('\n');
|
|
132
|
+
descLines.forEach((line) => {
|
|
133
|
+
formattedEntries.push(`${indentStr}${nestIndentStr}${comment} ${line.trim()}`);
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
// Format the key
|
|
137
|
+
const formattedKey = stripQuotes && /^[A-Za-z_$][0-9A-Za-z_$]*$/.test(key) ? key : JSON.stringify(key);
|
|
138
|
+
// Format the value
|
|
139
|
+
const formattedValue = formatWithDescriptions(val, resolvedPropSchema, rootSchema, comment, stripQuotes, indent + nestingIndent, nestingIndent, ignoreBinary, false);
|
|
140
|
+
formattedEntries.push(`${indentStr}${nestIndentStr}${formattedKey}: ${formattedValue}${index < entries.length - 1 ? ',' : ''}`);
|
|
141
|
+
});
|
|
142
|
+
return `{\n${formattedEntries.join('\n')}\n${indentStr}}`;
|
|
143
|
+
}
|
|
144
|
+
return JSON.stringify(value);
|
|
145
|
+
}
|
|
146
|
+
function resolveRef(ref, rootSchema) {
|
|
147
|
+
const path = ref.split('/').slice(1); // Remove the initial '#'
|
|
148
|
+
let current = rootSchema;
|
|
149
|
+
for (const segment of path) {
|
|
150
|
+
current = current[segment];
|
|
151
|
+
if (current === undefined) {
|
|
152
|
+
return {};
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return current;
|
|
156
|
+
}
|
|
157
|
+
function handleRef(ref, rootSchema, ignoreBinary) {
|
|
158
|
+
const resolved = resolveRef(ref, rootSchema);
|
|
159
|
+
return getSampleValue(resolved, rootSchema, ignoreBinary);
|
|
160
|
+
}
|
|
161
|
+
function handleString(schema) {
|
|
162
|
+
if (schema.format) {
|
|
163
|
+
switch (schema.format) {
|
|
164
|
+
case 'email':
|
|
165
|
+
case 'idn-email':
|
|
166
|
+
return 'user@example.com';
|
|
167
|
+
case 'uri':
|
|
168
|
+
case 'url':
|
|
169
|
+
case 'iri':
|
|
170
|
+
return 'https://example.com';
|
|
171
|
+
case 'date':
|
|
172
|
+
return '2023-01-01';
|
|
173
|
+
case 'date-time':
|
|
174
|
+
return '2023-01-01T00:00:00Z';
|
|
175
|
+
case 'time':
|
|
176
|
+
return '12:00:00Z';
|
|
177
|
+
case 'duration':
|
|
178
|
+
return 'PT1H';
|
|
179
|
+
case 'uuid':
|
|
180
|
+
return '00000000-0000-0000-0000-000000000000';
|
|
181
|
+
case 'regex':
|
|
182
|
+
return '^[a-zA-Z0-9]+$';
|
|
183
|
+
case 'relative-json-pointer':
|
|
184
|
+
return '/some/relative/path';
|
|
185
|
+
case 'color':
|
|
186
|
+
return '#000000';
|
|
187
|
+
case 'hostname':
|
|
188
|
+
return 'example.com';
|
|
189
|
+
case 'zipcode':
|
|
190
|
+
return '12345';
|
|
191
|
+
case 'phone':
|
|
192
|
+
return '+123-456-7890';
|
|
193
|
+
case 'password':
|
|
194
|
+
return '******';
|
|
195
|
+
case 'binary':
|
|
196
|
+
return 'binary-data';
|
|
197
|
+
default:
|
|
198
|
+
return 'string';
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
if (schema.pattern) {
|
|
202
|
+
return 'pattern-string';
|
|
203
|
+
}
|
|
204
|
+
return 'string';
|
|
205
|
+
}
|
|
206
|
+
function handleNumber(schema) {
|
|
207
|
+
if (schema.minimum !== undefined && schema.maximum !== undefined) {
|
|
208
|
+
return schema.minimum;
|
|
209
|
+
}
|
|
210
|
+
else if (schema.minimum !== undefined) {
|
|
211
|
+
return schema.minimum;
|
|
212
|
+
}
|
|
213
|
+
else if (schema.maximum !== undefined) {
|
|
214
|
+
return schema.maximum;
|
|
215
|
+
}
|
|
216
|
+
return 0;
|
|
217
|
+
}
|
|
218
|
+
function handleBoolean() {
|
|
219
|
+
return true;
|
|
220
|
+
}
|
|
221
|
+
function handleObject(schema, rootSchema, ignoreBinary) {
|
|
222
|
+
const result = {};
|
|
223
|
+
if (schema.properties) {
|
|
224
|
+
const required = schema.required || [];
|
|
225
|
+
for (const [key, propSchema] of Object.entries(schema.properties)) {
|
|
226
|
+
if (required.includes(key) || required.length === 0) {
|
|
227
|
+
const value = getSampleValue(propSchema, rootSchema, ignoreBinary);
|
|
228
|
+
// Only add the property if it's not undefined (which happens when ignoreBinary is true and it's a binary field)
|
|
229
|
+
if (value !== undefined) {
|
|
230
|
+
result[key] = value;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
if (schema.additionalProperties && typeof schema.additionalProperties === 'object') {
|
|
236
|
+
const value = getSampleValue(schema.additionalProperties, rootSchema, ignoreBinary);
|
|
237
|
+
if (value !== undefined) {
|
|
238
|
+
result.additionalProp = value;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
return result;
|
|
242
|
+
}
|
|
243
|
+
function handleArray(schema, rootSchema, ignoreBinary) {
|
|
244
|
+
if (schema.items) {
|
|
245
|
+
// If items is a boolean, return empty array (true means any items allowed, false means no items)
|
|
246
|
+
if (typeof schema.items === 'boolean') {
|
|
247
|
+
return schema.items ? [null] : [];
|
|
248
|
+
}
|
|
249
|
+
const itemSchema = schema.items;
|
|
250
|
+
// Check if the items are binary strings that should be ignored
|
|
251
|
+
if (ignoreBinary && itemSchema.type === 'string' && itemSchema.format === 'binary') {
|
|
252
|
+
return undefined;
|
|
253
|
+
}
|
|
254
|
+
const minItems = schema.minItems || 1;
|
|
255
|
+
const numItems = Math.min(minItems, 3);
|
|
256
|
+
const items = Array.from({ length: numItems }, () => getSampleValue(itemSchema, rootSchema, ignoreBinary)).filter((item) => item !== undefined); // Filter out undefined values from ignored binary items
|
|
257
|
+
// If all items were filtered out (e.g., all were binary), return undefined instead of empty array
|
|
258
|
+
if (items.length === 0 && numItems > 0) {
|
|
259
|
+
return undefined;
|
|
260
|
+
}
|
|
261
|
+
return items;
|
|
262
|
+
}
|
|
263
|
+
return [];
|
|
264
|
+
}
|