vovk-rust 0.0.1-draft.47 → 0.0.1-draft.49

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.
@@ -43,7 +43,7 @@ vars.convertJSONSchemasToRustTypes({
43
43
  validation?.output?.description ? `Returns: ${validation?.output?.description}`: ''
44
44
  ]).filter(Boolean).map((s) => s.split('\n')).flat().map((s) => ' '.repeat(4) + '/// ' + s).join('\n') %>
45
45
  pub fn <%= handlerName %>(
46
- body: <%- validation?.body ? (validation?.body?.['x-formData'] ?'reqwest::blocking::multipart::Form' : `${handlerName}_::body`): '()' %>,
46
+ body: <%- validation?.body ? (validation?.body?.['x-isForm'] ?'reqwest::blocking::multipart::Form' : `${handlerName}_::body`): '()' %>,
47
47
  query: <%- validation?.query ? `${handlerName}_::query` : '()' %>,
48
48
  params: <%- validation?.params ? `${handlerName}_::params` : '()' %>,
49
49
  headers: Option<&HashMap<String, String>>,
@@ -53,7 +53,7 @@ vars.convertJSONSchemasToRustTypes({
53
53
  let result = <%= validation?.iteration ? 'http_request_stream' : 'http_request' %>::<
54
54
  <%- [
55
55
  validation?.output ? `${handlerName}_::output` : validation?.iteration ? `${handlerName}_::iteration` : 'serde_json::Value',
56
- validation?.body && !validation?.body?.['x-formData'] ? `${handlerName}_::body` : '()',
56
+ validation?.body && !validation?.body?.['x-isForm'] ? `${handlerName}_::body` : '()',
57
57
  validation?.query ? `${handlerName}_::query` : '()',
58
58
  validation?.params ? `${handlerName}_::params` : '()'
59
59
  ].filter(Boolean).map((s) => ' '.repeat(12) + s).join(',\n') %>
@@ -62,8 +62,8 @@ vars.convertJSONSchemasToRustTypes({
62
62
  "<%= segmentName %>",
63
63
  "<%= controllerSchema.rpcModuleName %>",
64
64
  "<%= handlerNameOriginal %>",
65
- <%- !validation?.body || !validation?.body?.['x-formData'] ? `Some(&body)` : 'None' %>,
66
- <%- validation?.body && validation?.body?.['x-formData'] ? `Some(body)` : 'None' %>,
65
+ <%- !validation?.body || !validation?.body?.['x-isForm'] ? `Some(&body)` : 'None' %>,
66
+ <%- validation?.body && validation?.body?.['x-isForm'] ? `Some(body)` : 'None' %>,
67
67
  Some(&query),
68
68
  Some(&params),
69
69
  headers,
package/index.js CHANGED
@@ -1,504 +1,534 @@
1
1
  // Helper function for indentation
2
2
  export function indent(level, pad = 0) {
3
- return ' '.repeat(pad + level * 2);
3
+ return ' '.repeat(pad + level * 2);
4
4
  }
5
5
  // Generate documentation comments from title and description
6
6
  export function generateDocComment(schema, level, pad = 0) {
7
- if (!schema?.title && !schema?.description) return '';
8
- let comment = '';
9
- if (schema.title) {
10
- comment += `${indent(level, pad)}/// ${schema.title}\n`;
11
- if (schema.description) {
12
- comment += `${indent(level, pad)}///\n`;
7
+ if (!schema?.title && !schema?.description)
8
+ return '';
9
+ let comment = '';
10
+ if (schema.title) {
11
+ comment += `${indent(level, pad)}/// ${schema.title}\n`;
12
+ if (schema.description) {
13
+ comment += `${indent(level, pad)}///\n`;
14
+ }
13
15
  }
14
- }
15
- if (schema.description) {
16
- // Split description into lines and add /// to each line
17
- const lines = schema.description.split('\n');
18
- for (const line of lines) {
19
- comment += `${indent(level, pad)}/// ${line}\n`;
16
+ if (schema.description) {
17
+ // Split description into lines and add /// to each line
18
+ const lines = schema.description.split('\n');
19
+ for (const line of lines) {
20
+ comment += `${indent(level, pad)}/// ${line}\n`;
21
+ }
20
22
  }
21
- }
22
- return comment;
23
+ return comment;
23
24
  }
24
25
  // Track processed $refs to avoid circular references
25
26
  const processedRefs = new Set();
26
27
  // Resolve $ref paths in the schema
27
28
  export function resolveRef(ref, rootSchema) {
28
- if (processedRefs.has(ref)) {
29
- // Circular reference detected, return a placeholder
30
- return { type: 'string' };
31
- }
32
- processedRefs.add(ref);
33
- // Format: #/$defs/TypeName or #/components/schemas/TypeName or #/definitions/TypeName etc.
34
- const parts = ref.split('/').filter((part) => part && part !== '#');
35
- // Standard path traversal
36
- let current = rootSchema;
37
- for (const part of parts) {
38
- if (!current || typeof current !== 'object') {
39
- // If standard traversal fails and the path might be a definition reference
40
- if (parts.includes('definitions') && rootSchema.definitions) {
41
- // Try to access the definition directly using the last part as the key
42
- const definitionKey = parts[parts.length - 1];
43
- return rootSchema.definitions[definitionKey];
44
- }
45
- return undefined;
29
+ if (processedRefs.has(ref)) {
30
+ // Circular reference detected, return a placeholder
31
+ return { type: 'string' };
46
32
  }
47
- current = current[part];
48
- }
49
- // If the resolved schema also has a $ref, resolve it recursively
50
- if (current && current.$ref) {
51
- return resolveRef(current.$ref, rootSchema);
52
- }
53
- processedRefs.delete(ref);
54
- return current;
33
+ processedRefs.add(ref);
34
+ // Format: #/$defs/TypeName or #/components/schemas/TypeName or #/definitions/TypeName etc.
35
+ const parts = ref.split('/').filter((part) => part && part !== '#');
36
+ // Standard path traversal
37
+ let current = rootSchema;
38
+ for (const part of parts) {
39
+ if (!current || typeof current !== 'object') {
40
+ // If standard traversal fails and the path might be a definition reference
41
+ if (parts.includes('definitions') && rootSchema.definitions) {
42
+ // Try to access the definition directly using the last part as the key
43
+ const definitionKey = parts[parts.length - 1];
44
+ return rootSchema.definitions[definitionKey];
45
+ }
46
+ return undefined;
47
+ }
48
+ current = current[part];
49
+ }
50
+ // If the resolved schema also has a $ref, resolve it recursively
51
+ if (current && current.$ref) {
52
+ return resolveRef(current.$ref, rootSchema);
53
+ }
54
+ processedRefs.delete(ref);
55
+ return current;
55
56
  }
56
57
  // Generate module path for nested types
57
58
  export function getModulePath(path) {
58
- if (path.length <= 1) return path[0];
59
- let result = '';
60
- for (let i = 0; i < path.length - 1; i++) {
61
- result += path[i] + '_::';
62
- }
63
- result += path[path.length - 1];
64
- return result;
59
+ if (path.length <= 1)
60
+ return path[0];
61
+ let result = '';
62
+ for (let i = 0; i < path.length - 1; i++) {
63
+ result += path[i] + '_::';
64
+ }
65
+ result += path[path.length - 1];
66
+ return result;
65
67
  }
66
68
  // Convert JSON Schema type to Rust type
67
69
  export function toRustType(schema, path, rootSchema = schema) {
68
- if (!schema) return 'String';
69
- // Handle $ref first
70
- if (schema.$ref) {
71
- const resolvedSchema = resolveRef(schema.$ref, rootSchema);
72
- if (resolvedSchema) {
73
- // Extract type name from ref path for better type naming
74
- const refName = schema.$ref.split('/').pop();
75
- // Use the ref name directly if it's a definition reference
76
- if (schema.$ref.includes('definitions/') || schema.$ref.includes('$defs/')) {
77
- return refName || 'String';
78
- }
79
- return toRustType(resolvedSchema, refName ? [...path.slice(0, -1), refName] : path, rootSchema);
70
+ if (!schema)
71
+ return 'String';
72
+ // Handle $ref first
73
+ if (schema.$ref) {
74
+ const resolvedSchema = resolveRef(schema.$ref, rootSchema);
75
+ if (resolvedSchema) {
76
+ // Extract type name from ref path for better type naming
77
+ const refName = schema.$ref.split('/').pop();
78
+ // Use the ref name directly if it's a definition reference
79
+ if (schema.$ref.includes('definitions/') || schema.$ref.includes('$defs/')) {
80
+ return refName || 'String';
81
+ }
82
+ return toRustType(resolvedSchema, refName ? [...path.slice(0, -1), refName] : path, rootSchema);
83
+ }
84
+ return 'serde_json::Value'; // Fallback for unresolved $ref
85
+ }
86
+ // Check for enum without type (assume string)
87
+ if (schema.enum) {
88
+ if (schema.type === 'string' || !schema.type) {
89
+ return `${getModulePath(path)}`;
90
+ }
80
91
  }
81
- return 'serde_json::Value'; // Fallback for unresolved $ref
82
- }
83
- // Check for enum without type (assume string)
84
- if (schema.enum) {
85
- if (schema.type === 'string' || !schema.type) {
86
- return `${getModulePath(path)}`;
92
+ if (schema.type === 'string') {
93
+ return 'String';
87
94
  }
88
- }
89
- if (schema.type === 'string') {
90
- return 'String';
91
- } else if (schema.type === 'number' || schema.type === 'integer') {
92
- // Handle numeric types with constraints
93
- if (schema.type === 'integer') {
94
- // Determine integer type based on min/max constraints
95
- const min =
96
- typeof schema.minimum === 'number'
97
- ? schema.minimum
98
- : typeof schema.exclusiveMinimum === 'number'
99
- ? schema.exclusiveMinimum + 1
100
- : undefined;
101
- const max =
102
- typeof schema.maximum === 'number'
103
- ? schema.maximum
104
- : typeof schema.exclusiveMaximum === 'number'
105
- ? schema.exclusiveMaximum - 1
106
- : undefined;
107
- // Check if we need unsigned (no negative values)
108
- if (min !== undefined && min >= 0) {
109
- // Choose appropriate unsigned int size
110
- if (max !== undefined) {
111
- if (max <= 255) return 'u8';
112
- if (max <= 65535) return 'u16';
113
- if (max <= 4294967295) return 'u32';
95
+ else if (schema.type === 'number' || schema.type === 'integer') {
96
+ // Handle numeric types with constraints
97
+ if (schema.type === 'integer') {
98
+ // Determine integer type based on min/max constraints
99
+ const min = typeof schema.minimum === 'number'
100
+ ? schema.minimum
101
+ : typeof schema.exclusiveMinimum === 'number'
102
+ ? schema.exclusiveMinimum + 1
103
+ : undefined;
104
+ const max = typeof schema.maximum === 'number'
105
+ ? schema.maximum
106
+ : typeof schema.exclusiveMaximum === 'number'
107
+ ? schema.exclusiveMaximum - 1
108
+ : undefined;
109
+ // Check if we need unsigned (no negative values)
110
+ if (min !== undefined && min >= 0) {
111
+ // Choose appropriate unsigned int size
112
+ if (max !== undefined) {
113
+ if (max <= 255)
114
+ return 'u8';
115
+ if (max <= 65535)
116
+ return 'u16';
117
+ if (max <= 4294967295)
118
+ return 'u32';
119
+ }
120
+ return 'u64'; // Default unsigned
121
+ }
122
+ else {
123
+ // Choose appropriate signed int size
124
+ if (min !== undefined && max !== undefined) {
125
+ const absMin = Math.abs(min);
126
+ const absMax = Math.abs(max);
127
+ const maxVal = Math.max(absMin - 1, absMax);
128
+ if (maxVal <= 127)
129
+ return 'i8';
130
+ if (maxVal <= 32767)
131
+ return 'i16';
132
+ if (maxVal <= 2147483647)
133
+ return 'i32';
134
+ }
135
+ return 'i64'; // Default signed
136
+ }
114
137
  }
115
- return 'u64'; // Default unsigned
116
- } else {
117
- // Choose appropriate signed int size
118
- if (min !== undefined && max !== undefined) {
119
- const absMin = Math.abs(min);
120
- const absMax = Math.abs(max);
121
- const maxVal = Math.max(absMin - 1, absMax);
122
- if (maxVal <= 127) return 'i8';
123
- if (maxVal <= 32767) return 'i16';
124
- if (maxVal <= 2147483647) return 'i32';
138
+ else {
139
+ // Floating point
140
+ const hasLowRange = (schema.minimum !== undefined &&
141
+ schema.maximum !== undefined &&
142
+ Math.abs(schema.maximum - schema.minimum) <= 3.4e38) ||
143
+ (schema.exclusiveMinimum !== undefined &&
144
+ schema.exclusiveMaximum !== undefined &&
145
+ Math.abs(schema.exclusiveMaximum - schema.exclusiveMinimum) <= 3.4e38);
146
+ return hasLowRange ? 'f32' : 'f64';
125
147
  }
126
- return 'i64'; // Default signed
127
- }
128
- } else {
129
- // Floating point
130
- const hasLowRange =
131
- (schema.minimum !== undefined &&
132
- schema.maximum !== undefined &&
133
- Math.abs(schema.maximum - schema.minimum) <= 3.4e38) ||
134
- (schema.exclusiveMinimum !== undefined &&
135
- schema.exclusiveMaximum !== undefined &&
136
- Math.abs(schema.exclusiveMaximum - schema.exclusiveMinimum) <= 3.4e38);
137
- return hasLowRange ? 'f32' : 'f64';
138
148
  }
139
- } else if (schema.type === 'boolean') {
140
- return 'bool';
141
- } else if (schema.type === 'null') {
142
- return '()';
143
- } else if (schema.type === 'array') {
144
- if (schema.items) {
145
- // Check if array items are objects that need special handling
146
- if (schema.items.type === 'object' || schema.items.properties || schema.items.$ref) {
147
- // For array of objects, reference the item type with proper module path
148
- // Find the parent module name to reference the item
149
- const parentName = path[path.length - 2] || path[0];
150
- return `Vec<${parentName}_::${path[path.length - 1]}Item>`;
151
- }
152
- const itemType = toRustType(schema.items, [...path, '_item'], rootSchema);
153
- return `Vec<${itemType}>`;
149
+ else if (schema.type === 'boolean') {
150
+ return 'bool';
154
151
  }
155
- return 'Vec<String>';
156
- } else if (schema.type === 'object' || schema.properties) {
157
- // Handle empty objects
158
- if (schema.type === 'object' && (!schema.properties || Object.keys(schema.properties).length === 0)) {
159
- return 'serde_json::Value';
152
+ else if (schema.type === 'null') {
153
+ return '()';
154
+ }
155
+ else if (schema.type === 'array') {
156
+ if (schema.items) {
157
+ // Check if array items are objects that need special handling
158
+ if (schema.items.type === 'object' || schema.items.properties || schema.items.$ref) {
159
+ // For array of objects, reference the item type with proper module path
160
+ // Find the parent module name to reference the item
161
+ const parentName = path[path.length - 2] || path[0];
162
+ return `Vec<${parentName}_::${path[path.length - 1]}Item>`;
163
+ }
164
+ const itemType = toRustType(schema.items, [...path, '_item'], rootSchema);
165
+ return `Vec<${itemType}>`;
166
+ }
167
+ return 'Vec<String>';
160
168
  }
161
- return path[path.length - 1];
162
- } else if (schema.anyOf || schema.oneOf || schema.allOf) {
163
- return `${getModulePath(path)}`;
164
- }
165
- return 'String'; // Default fallback
169
+ else if (schema.type === 'object' || schema.properties) {
170
+ // Handle empty objects
171
+ if (schema.type === 'object' && (!schema.properties || Object.keys(schema.properties).length === 0)) {
172
+ return 'serde_json::Value';
173
+ }
174
+ return path[path.length - 1];
175
+ }
176
+ else if (schema.anyOf || schema.oneOf || schema.allOf) {
177
+ return `${getModulePath(path)}`;
178
+ }
179
+ return 'String'; // Default fallback
166
180
  }
167
181
  // Generate enum for string with enum values
168
182
  export function generateEnum(schema, name, level, pad = 0) {
169
- const indentFn = (level) => ' '.repeat(pad + level * 2);
170
- let code = '';
171
- // Add documentation comments for the enum
172
- code += generateDocComment(schema, level, pad);
173
- code += `${indentFn(level)}#[derive(Debug, Serialize, Deserialize, Clone)]\n`;
174
- code += `${indentFn(level)}#[allow(non_camel_case_types)]\n`;
175
- code += `${indentFn(level)}pub enum ${name} {\n`;
176
- schema.enum.forEach((value, index) => {
177
- // Create valid Rust enum variant
178
- const variant = value.replace(/[^a-zA-Z0-9_]/g, '_');
179
- // Add documentation if available in enumDescriptions
180
- if (schema.enumDescriptions && schema.enumDescriptions[index]) {
181
- const description = schema.enumDescriptions[index];
182
- code += `${indentFn(level + 1)}/// ${description}\n`;
183
- }
184
- code += `${indentFn(level + 1)}#[serde(rename = "${value}")]\n`;
185
- code += `${indentFn(level + 1)}${variant},\n`;
186
- });
187
- code += `${indentFn(level)}}\n\n`;
188
- return code;
183
+ const indentFn = (level) => ' '.repeat(pad + level * 2);
184
+ let code = '';
185
+ // Add documentation comments for the enum
186
+ code += generateDocComment(schema, level, pad);
187
+ code += `${indentFn(level)}#[derive(Debug, Serialize, Deserialize, Clone)]\n`;
188
+ code += `${indentFn(level)}#[allow(non_camel_case_types)]\n`;
189
+ code += `${indentFn(level)}pub enum ${name} {\n`;
190
+ schema.enum.forEach((value, index) => {
191
+ // Create valid Rust enum variant
192
+ const variant = value.replace(/[^a-zA-Z0-9_]/g, '_');
193
+ // Add documentation if available in enumDescriptions
194
+ if (schema.enumDescriptions && schema.enumDescriptions[index]) {
195
+ const description = schema.enumDescriptions[index];
196
+ code += `${indentFn(level + 1)}/// ${description}\n`;
197
+ }
198
+ code += `${indentFn(level + 1)}#[serde(rename = "${value}")]\n`;
199
+ code += `${indentFn(level + 1)}${variant},\n`;
200
+ });
201
+ code += `${indentFn(level)}}\n\n`;
202
+ return code;
189
203
  }
190
204
  // Generate enum for anyOf/oneOf/allOf schemas
191
205
  export function generateVariantEnum(schema, name, path, level, rootSchema, pad = 0) {
192
- const indentFn = (level) => ' '.repeat(pad + level * 2);
193
- // Handle allOf separately - it should combine schemas rather than create variants
194
- if (schema.allOf) {
195
- return generateAllOfType(schema.allOf, name, path, level, rootSchema, pad);
196
- }
197
- const variants = schema.anyOf || schema.oneOf || [];
198
- let code = '';
199
- let nestedTypes = '';
200
- // Add documentation comments for the enum
201
- code += generateDocComment(schema, level, pad);
202
- code += `${indentFn(level)}#[derive(Debug, Serialize, Deserialize, Clone)]\n`;
203
- code += `${indentFn(level)}#[allow(non_camel_case_types)]\n`;
204
- code += `${indentFn(level)}#[serde(untagged)]\n`;
205
- code += `${indentFn(level)}pub enum ${name} {\n`;
206
- variants.forEach((variant, index) => {
207
- // Resolve $ref if present
208
- if (variant.$ref) {
209
- const resolved = resolveRef(variant.$ref, rootSchema);
210
- if (resolved) {
211
- variant = resolved;
212
- }
206
+ const indentFn = (level) => ' '.repeat(pad + level * 2);
207
+ // Handle allOf separately - it should combine schemas rather than create variants
208
+ if (schema.allOf) {
209
+ return generateAllOfType(schema.allOf, name, path, level, rootSchema, pad);
213
210
  }
214
- const variantName = `Variant${index}`;
215
- const variantPath = [...path, name, variantName];
216
- // If it's an object type, we need to create a separate struct
217
- if (variant.type === 'object' || variant.properties) {
218
- code += `${indentFn(level + 1)}${variantName}(${name}_::${variantName}),\n`;
219
- // Create a nested type definition to be added outside the enum
220
- nestedTypes += processObject(variant, variantPath, level, rootSchema, pad);
221
- } else {
222
- // For simple types, we can include them directly in the enum
223
- const variantType = toRustType(variant, variantPath, rootSchema);
224
- code += `${indentFn(level + 1)}${variantName}(${variantType}),\n`;
211
+ const variants = schema.anyOf || schema.oneOf || [];
212
+ let code = '';
213
+ let nestedTypes = '';
214
+ // Add documentation comments for the enum
215
+ code += generateDocComment(schema, level, pad);
216
+ code += `${indentFn(level)}#[derive(Debug, Serialize, Deserialize, Clone)]\n`;
217
+ code += `${indentFn(level)}#[allow(non_camel_case_types)]\n`;
218
+ code += `${indentFn(level)}#[serde(untagged)]\n`;
219
+ code += `${indentFn(level)}pub enum ${name} {\n`;
220
+ variants.forEach((variant, index) => {
221
+ // Resolve $ref if present
222
+ if (variant.$ref) {
223
+ const resolved = resolveRef(variant.$ref, rootSchema);
224
+ if (resolved) {
225
+ variant = resolved;
226
+ }
227
+ }
228
+ const variantName = `Variant${index}`;
229
+ const variantPath = [...path, name, variantName];
230
+ // If it's an object type, we need to create a separate struct
231
+ if (variant.type === 'object' || variant.properties) {
232
+ code += `${indentFn(level + 1)}${variantName}(${name}_::${variantName}),\n`;
233
+ // Create a nested type definition to be added outside the enum
234
+ nestedTypes += processObject(variant, variantPath, level, rootSchema, pad);
235
+ }
236
+ else {
237
+ // For simple types, we can include them directly in the enum
238
+ const variantType = toRustType(variant, variantPath, rootSchema);
239
+ code += `${indentFn(level + 1)}${variantName}(${variantType}),\n`;
240
+ }
241
+ });
242
+ code += `${indentFn(level)}}\n\n`;
243
+ // Add nested type definitions if needed
244
+ if (nestedTypes) {
245
+ code += nestedTypes;
225
246
  }
226
- });
227
- code += `${indentFn(level)}}\n\n`;
228
- // Add nested type definitions if needed
229
- if (nestedTypes) {
230
- code += nestedTypes;
231
- }
232
- return code;
247
+ return code;
233
248
  }
234
249
  // Handle allOf schema by merging properties
235
250
  export function generateAllOfType(schemas, name, path, level, rootSchema, pad = 0) {
236
- const mergedSchema = {
237
- type: 'object',
238
- properties: {},
239
- required: [],
240
- };
241
- // Merge all schemas in allOf
242
- schemas.forEach((schema) => {
243
- // Resolve $ref if present
244
- if (schema.$ref) {
245
- const resolved = resolveRef(schema.$ref, rootSchema);
246
- if (resolved) {
247
- schema = resolved;
248
- }
249
- }
250
- if (schema.properties) {
251
- mergedSchema.properties = {
252
- ...mergedSchema.properties,
253
- ...schema.properties,
254
- };
255
- }
256
- if (schema.required) {
257
- mergedSchema.required = [...mergedSchema.required, ...schema.required];
258
- }
259
- });
260
- // Process the merged schema as a regular object
261
- return processObject(mergedSchema, [...path, name], level, rootSchema, pad);
251
+ const mergedSchema = {
252
+ type: 'object',
253
+ properties: {},
254
+ required: [],
255
+ };
256
+ // Merge all schemas in allOf
257
+ schemas.forEach((schema) => {
258
+ // Resolve $ref if present
259
+ if (schema.$ref) {
260
+ const resolved = resolveRef(schema.$ref, rootSchema);
261
+ if (resolved) {
262
+ schema = resolved;
263
+ }
264
+ }
265
+ if (schema.properties) {
266
+ mergedSchema.properties = {
267
+ ...mergedSchema.properties,
268
+ ...schema.properties,
269
+ };
270
+ }
271
+ if (schema.required) {
272
+ mergedSchema.required = [...mergedSchema.required, ...schema.required];
273
+ }
274
+ });
275
+ // Process the merged schema as a regular object
276
+ return processObject(mergedSchema, [...path, name], level, rootSchema, pad);
262
277
  }
263
278
  // Process schema objects and generate Rust code
264
279
  export function processObject(schema, path, level, rootSchema = schema, pad = 0) {
265
- const indentFn = (level) => ' '.repeat(pad + level * 2);
266
- if (!schema) {
267
- return '';
268
- }
269
- // Handle empty objects
270
- if (schema.type === 'object' && (!schema.properties || Object.keys(schema.properties).length === 0)) {
271
- // Empty object is handled as serde_json::Value at the field level
272
- return '';
273
- }
274
- if (!schema.properties) {
275
- return '';
276
- }
277
- const currentName = path[path.length - 1];
278
- let code = '';
279
- // Add documentation comments for the struct
280
- code += generateDocComment(schema, level, pad);
281
- if (schema.type === 'object' && schema['x-formData'] === true) {
282
- code += `${indentFn(level)}pub use reqwest::multipart::Form as ${currentName};\n`;
283
- return code;
284
- }
285
- // Generate struct
286
- code += `${indentFn(level)}#[derive(Debug, Serialize, Deserialize, Clone)]\n`;
287
- code += `${indentFn(level)}#[allow(non_snake_case, non_camel_case_types)]\n`;
288
- code += `${indentFn(level)}pub struct ${currentName} {\n`;
289
- // Generate struct fields
290
- Object.entries(schema.properties).forEach(([propName, propSchema]) => {
291
- const isRequired = schema.required && schema.required.includes(propName);
292
- // Handle $ref in property
293
- if (propSchema.$ref) {
294
- const resolvedSchema = resolveRef(propSchema.$ref, rootSchema);
295
- if (resolvedSchema) {
296
- propSchema = resolvedSchema;
297
- }
280
+ const indentFn = (level) => ' '.repeat(pad + level * 2);
281
+ if (!schema) {
282
+ return '';
298
283
  }
299
- // Add documentation comments for the field
300
- code += generateDocComment(propSchema, level + 1, pad);
301
- // Determine if this property is a nested type that should be accessed via module path
302
- const isNestedObject = propSchema.type === 'object' || propSchema.properties;
303
- const isGenericObject = propSchema.type === 'object' && !propSchema.properties;
304
- // Define nested enum types
305
- const isNestedEnum =
306
- ((propSchema.type === 'string' || !propSchema.type) && propSchema.enum) ||
307
- propSchema.anyOf ||
308
- propSchema.oneOf ||
309
- propSchema.allOf;
310
- const propPath = [...path, propName];
311
- let propType;
312
- if (isGenericObject) {
313
- // For generic objects, we can use serde_json::Value
314
- propType = 'serde_json::Value';
315
- } else if (isNestedObject || isNestedEnum) {
316
- // For nested objects and enums, we need to reference them via their module path
317
- propType = `${currentName}_::${propName}`;
318
- // Special case for enums which have a different naming convention
319
- if (isNestedEnum) {
320
- propType += ''; // 'Enum';
321
- }
322
- } else {
323
- // For other types, use the standard type resolution
324
- propType = toRustType(propSchema, propPath, rootSchema);
284
+ // Handle empty objects
285
+ if (schema.type === 'object' && (!schema.properties || Object.keys(schema.properties).length === 0)) {
286
+ // Empty object is handled as serde_json::Value at the field level
287
+ return '';
325
288
  }
326
- if (!isRequired) {
327
- propType = `Option<${propType}>`;
289
+ if (!schema.properties) {
290
+ return '';
328
291
  }
329
- code += `${indentFn(level + 1)}pub ${propName}: ${propType},\n`;
330
- });
331
- code += `${indentFn(level)}}\n\n`;
332
- // Check if any properties require nested types before generating the sub-module
333
- const hasNestedTypes = Object.entries(schema.properties).some(([, propSchema]) => {
334
- // Resolve $ref if present
335
- if (propSchema.$ref) {
336
- const resolved = resolveRef(propSchema.$ref, rootSchema);
337
- propSchema = resolved || propSchema;
292
+ const currentName = path[path.length - 1];
293
+ let code = '';
294
+ // Add documentation comments for the struct
295
+ code += generateDocComment(schema, level, pad);
296
+ if (schema.type === 'object' && schema['x-isForm'] === true) {
297
+ code += `${indentFn(level)}pub use reqwest::multipart::Form as ${currentName};\n`;
298
+ return code;
338
299
  }
339
- return (
340
- propSchema.type === 'object' ||
341
- propSchema.properties ||
342
- ((propSchema.type === 'string' || !propSchema.type) && propSchema.enum) ||
343
- (propSchema.type === 'array' &&
344
- propSchema.items &&
345
- (propSchema.items.type === 'object' || propSchema.items.properties || propSchema.items.$ref)) ||
346
- propSchema.anyOf ||
347
- propSchema.oneOf ||
348
- propSchema.allOf
349
- );
350
- });
351
- // Only generate sub-modules if there are nested types
352
- if (hasNestedTypes && Object.keys(schema.properties).length > 0) {
353
- code += `${indentFn(level)}#[allow(non_snake_case)]\n`;
354
- code += `${indentFn(level)}pub mod ${currentName}_ {\n`;
355
- code += `${indentFn(level + 1)}use serde::{Serialize, Deserialize};\n\n`;
300
+ // Generate struct
301
+ code += `${indentFn(level)}#[derive(Debug, Serialize, Deserialize, Clone)]\n`;
302
+ code += `${indentFn(level)}#[allow(non_snake_case, non_camel_case_types)]\n`;
303
+ code += `${indentFn(level)}pub struct ${currentName} {\n`;
304
+ // Generate struct fields
356
305
  Object.entries(schema.properties).forEach(([propName, propSchema]) => {
357
- // Resolve $ref if present
358
- if (propSchema.$ref) {
359
- const resolved = resolveRef(propSchema.$ref, rootSchema);
360
- if (resolved) {
361
- propSchema = resolved;
306
+ const isRequired = schema.required && schema.required.includes(propName);
307
+ // Handle $ref in property
308
+ if (propSchema.$ref) {
309
+ const resolvedSchema = resolveRef(propSchema.$ref, rootSchema);
310
+ if (resolvedSchema) {
311
+ propSchema = resolvedSchema;
312
+ }
362
313
  }
363
- }
364
- // Generate nested object types
365
- if (propSchema.type === 'object' || propSchema.properties) {
366
- code += processObject(propSchema, [...path, propName], level + 1, rootSchema, pad);
367
- }
368
- // Generate enum types for string enums (also when type is missing but enum exists)
369
- else if ((propSchema.type === 'string' || !propSchema.type) && propSchema.enum) {
370
- code += generateEnum(propSchema, propName, level + 1, pad);
371
- }
372
- // Generate types for array items if they're objects
373
- else if (propSchema.type === 'array' && propSchema.items) {
374
- // Check if items has a $ref
375
- if (propSchema.items.$ref) {
376
- const resolved = resolveRef(propSchema.items.$ref, rootSchema);
377
- if (resolved && (resolved.type === 'object' || resolved.properties)) {
378
- code += processObject(resolved, [...path, propName + 'Item'], level + 1, rootSchema, pad);
379
- }
380
- } else if (propSchema.items.type === 'object' || propSchema.items.properties) {
381
- code += processObject(propSchema.items, [...path, propName + 'Item'], level + 1, rootSchema, pad);
314
+ // Add documentation comments for the field
315
+ code += generateDocComment(propSchema, level + 1, pad);
316
+ // Determine if this property is a nested type that should be accessed via module path
317
+ const isNestedObject = propSchema.type === 'object' || propSchema.properties;
318
+ const isGenericObject = propSchema.type === 'object' && !propSchema.properties;
319
+ // Define nested enum types
320
+ const isNestedEnum = ((propSchema.type === 'string' || !propSchema.type) && propSchema.enum) ||
321
+ propSchema.anyOf ||
322
+ propSchema.oneOf ||
323
+ propSchema.allOf;
324
+ const propPath = [...path, propName];
325
+ let propType;
326
+ if (isGenericObject) {
327
+ // For generic objects, we can use serde_json::Value
328
+ propType = 'serde_json::Value';
382
329
  }
383
- }
384
- // Handle anyOf/oneOf/allOf schemas
385
- else if (propSchema.anyOf || propSchema.oneOf || propSchema.allOf) {
386
- code += generateVariantEnum(propSchema, propName, path, level + 1, rootSchema, pad);
387
- }
330
+ else if (isNestedObject || isNestedEnum) {
331
+ // For nested objects and enums, we need to reference them via their module path
332
+ propType = `${currentName}_::${propName}`;
333
+ // Special case for enums which have a different naming convention
334
+ if (isNestedEnum) {
335
+ propType += ''; // 'Enum';
336
+ }
337
+ }
338
+ else {
339
+ // For other types, use the standard type resolution
340
+ propType = toRustType(propSchema, propPath, rootSchema);
341
+ }
342
+ if (!isRequired) {
343
+ propType = `Option<${propType}>`;
344
+ }
345
+ code += `${indentFn(level + 1)}pub ${propName}: ${propType},\n`;
346
+ });
347
+ code += `${indentFn(level)}}\n\n`;
348
+ // Check if any properties require nested types before generating the sub-module
349
+ const hasNestedTypes = Object.entries(schema.properties).some(([, propSchema]) => {
350
+ // Resolve $ref if present
351
+ if (propSchema.$ref) {
352
+ const resolved = resolveRef(propSchema.$ref, rootSchema);
353
+ propSchema = resolved || propSchema;
354
+ }
355
+ return (propSchema.type === 'object' ||
356
+ propSchema.properties ||
357
+ ((propSchema.type === 'string' || !propSchema.type) && propSchema.enum) ||
358
+ (propSchema.type === 'array' &&
359
+ propSchema.items &&
360
+ (propSchema.items.type === 'object' || propSchema.items.properties || propSchema.items.$ref)) ||
361
+ propSchema.anyOf ||
362
+ propSchema.oneOf ||
363
+ propSchema.allOf);
388
364
  });
389
- code += `${indentFn(level)}}\n`;
390
- }
391
- return code;
365
+ // Only generate sub-modules if there are nested types
366
+ if (hasNestedTypes && Object.keys(schema.properties).length > 0) {
367
+ code += `${indentFn(level)}#[allow(non_snake_case)]\n`;
368
+ code += `${indentFn(level)}pub mod ${currentName}_ {\n`;
369
+ code += `${indentFn(level + 1)}use serde::{Serialize, Deserialize};\n\n`;
370
+ Object.entries(schema.properties).forEach(([propName, propSchema]) => {
371
+ // Resolve $ref if present
372
+ if (propSchema.$ref) {
373
+ const resolved = resolveRef(propSchema.$ref, rootSchema);
374
+ if (resolved) {
375
+ propSchema = resolved;
376
+ }
377
+ }
378
+ // Generate nested object types
379
+ if (propSchema.type === 'object' || propSchema.properties) {
380
+ code += processObject(propSchema, [...path, propName], level + 1, rootSchema, pad);
381
+ }
382
+ // Generate enum types for string enums (also when type is missing but enum exists)
383
+ else if ((propSchema.type === 'string' || !propSchema.type) && propSchema.enum) {
384
+ code += generateEnum(propSchema, propName, level + 1, pad);
385
+ }
386
+ // Generate types for array items if they're objects
387
+ else if (propSchema.type === 'array' && propSchema.items) {
388
+ // Check if items has a $ref
389
+ if (propSchema.items.$ref) {
390
+ const resolved = resolveRef(propSchema.items.$ref, rootSchema);
391
+ if (resolved && (resolved.type === 'object' || resolved.properties)) {
392
+ code += processObject(resolved, [...path, propName + 'Item'], level + 1, rootSchema, pad);
393
+ }
394
+ }
395
+ else if (propSchema.items.type === 'object' || propSchema.items.properties) {
396
+ code += processObject(propSchema.items, [...path, propName + 'Item'], level + 1, rootSchema, pad);
397
+ }
398
+ }
399
+ // Handle anyOf/oneOf/allOf schemas
400
+ else if (propSchema.anyOf || propSchema.oneOf || propSchema.allOf) {
401
+ code += generateVariantEnum(propSchema, propName, path, level + 1, rootSchema, pad);
402
+ }
403
+ });
404
+ code += `${indentFn(level)}}\n`;
405
+ }
406
+ return code;
392
407
  }
393
408
  // Generate code for primitive types
394
409
  export function processPrimitive(schema, name, level, pad = 0) {
395
- const indentFn = (level) => ' '.repeat(pad + level * 2);
396
- let code = '';
397
- // Add documentation comments
398
- code += generateDocComment(schema, level, pad);
399
- // For primitive types, create a type alias
400
- code += `${indentFn(level)}pub type ${name} = `;
401
- if (schema.type === 'string') {
402
- code += 'String';
403
- } else if (schema.type === 'number') {
404
- code += 'f64';
405
- } else if (schema.type === 'integer') {
406
- code += 'i64';
407
- } else if (schema.type === 'boolean') {
408
- code += 'bool';
409
- } else if (schema.type === 'null') {
410
- code += '()';
411
- } else if (schema.enum) {
412
- // If it has enum values, we'll generate an actual enum instead of a type alias
413
- return generateEnum(schema, name, level, pad);
414
- } else {
415
- code += 'String'; // Default fallback
416
- }
417
- code += ';\n\n';
418
- return code;
410
+ const indentFn = (level) => ' '.repeat(pad + level * 2);
411
+ let code = '';
412
+ // Add documentation comments
413
+ code += generateDocComment(schema, level, pad);
414
+ // For primitive types, create a type alias
415
+ code += `${indentFn(level)}pub type ${name} = `;
416
+ if (schema.type === 'string') {
417
+ code += 'String';
418
+ }
419
+ else if (schema.type === 'number') {
420
+ code += 'f64';
421
+ }
422
+ else if (schema.type === 'integer') {
423
+ code += 'i64';
424
+ }
425
+ else if (schema.type === 'boolean') {
426
+ code += 'bool';
427
+ }
428
+ else if (schema.type === 'null') {
429
+ code += '()';
430
+ }
431
+ else if (schema.enum) {
432
+ // If it has enum values, we'll generate an actual enum instead of a type alias
433
+ return generateEnum(schema, name, level, pad);
434
+ }
435
+ else {
436
+ code += 'String'; // Default fallback
437
+ }
438
+ code += ';\n\n';
439
+ return code;
419
440
  }
420
- export function convertJSONSchemasToRustTypes({ schemas, pad = 0, rootName }) {
421
- // Check if all schemas are undefined
422
- const hasDefinedSchemas = Object.values(schemas).some((schema) => schema !== undefined);
423
- if (!hasDefinedSchemas) {
424
- return '';
425
- }
426
- const indentFn = (level) => ' '.repeat(pad + level * 2);
427
- // Start code generation
428
- let result = `${indentFn(0)}pub mod ${rootName}_ {\n`;
429
- result += `${indentFn(1)}use serde::{Serialize, Deserialize};\n`;
430
- // Process each schema in the schemas object
431
- Object.entries(schemas).forEach(([schemaName, schemaObj]) => {
432
- // Skip undefined schemas
433
- if (!schemaObj) return;
434
- // Extract and process types from $defs if present
435
- if (schemaObj.$defs) {
436
- Object.entries(schemaObj.$defs).forEach(([defName, defSchema]) => {
437
- // Create a root object for each definition
438
- if (defSchema) {
439
- if (defSchema.type === 'object' || defSchema.properties) {
440
- const rootDefObject = {
441
- type: 'object',
442
- properties: defSchema.properties || {},
443
- required: defSchema.required || [],
444
- title: defSchema.title,
445
- description: defSchema.description,
446
- 'x-formData': defSchema['x-formData'],
441
+ export function convertJSONSchemasToRustTypes({ schemas, pad = 0, rootName, }) {
442
+ // Check if all schemas are undefined
443
+ const hasDefinedSchemas = Object.values(schemas).some((schema) => schema !== undefined);
444
+ if (!hasDefinedSchemas) {
445
+ return '';
446
+ }
447
+ const indentFn = (level) => ' '.repeat(pad + level * 2);
448
+ // Start code generation
449
+ let result = `${indentFn(0)}pub mod ${rootName}_ {\n`;
450
+ result += `${indentFn(1)}use serde::{Serialize, Deserialize};\n`;
451
+ // Process each schema in the schemas object
452
+ Object.entries(schemas).forEach(([schemaName, schemaObj]) => {
453
+ // Skip undefined schemas
454
+ if (!schemaObj)
455
+ return;
456
+ // Extract and process types from $defs if present
457
+ if (schemaObj.$defs) {
458
+ Object.entries(schemaObj.$defs).forEach(([defName, defSchema]) => {
459
+ // Create a root object for each definition
460
+ if (defSchema) {
461
+ if (defSchema.type === 'object' || defSchema.properties) {
462
+ const rootDefObject = {
463
+ type: 'object',
464
+ properties: defSchema.properties || {},
465
+ required: defSchema.required || [],
466
+ title: defSchema.title,
467
+ description: defSchema.description,
468
+ 'x-isForm': defSchema['x-isForm'],
469
+ };
470
+ result += processObject(rootDefObject, [defName], 1, schemaObj, pad);
471
+ }
472
+ else if (defSchema.type === 'string' && defSchema.enum) {
473
+ result += generateEnum(defSchema, defName, 1, pad);
474
+ }
475
+ else if (defSchema.anyOf || defSchema.oneOf || defSchema.allOf) {
476
+ result += generateVariantEnum(defSchema, defName, [defName], 1, schemaObj, pad);
477
+ }
478
+ else if (defSchema.type && ['string', 'number', 'integer', 'boolean', 'null'].includes(defSchema.type)) {
479
+ // Handle primitive types in $defs
480
+ result += processPrimitive(defSchema, defName, 1, pad);
481
+ }
482
+ }
483
+ });
484
+ }
485
+ // Handle the schema based on its type
486
+ if (schemaObj.type === 'object' || schemaObj.properties) {
487
+ // Create a root object for object schema
488
+ const rootObject = {
489
+ type: 'object',
490
+ properties: schemaObj.properties || {},
491
+ required: schemaObj.required || [],
492
+ title: schemaObj.title,
493
+ description: schemaObj.description,
494
+ 'x-isForm': schemaObj['x-isForm'],
447
495
  };
448
- result += processObject(rootDefObject, [defName], 1, schemaObj, pad);
449
- } else if (defSchema.type === 'string' && defSchema.enum) {
450
- result += generateEnum(defSchema, defName, 1, pad);
451
- } else if (defSchema.anyOf || defSchema.oneOf || defSchema.allOf) {
452
- result += generateVariantEnum(defSchema, defName, [defName], 1, schemaObj, pad);
453
- } else if (defSchema.type && ['string', 'number', 'integer', 'boolean', 'null'].includes(defSchema.type)) {
454
- // Handle primitive types in $defs
455
- result += processPrimitive(defSchema, defName, 1, pad);
456
- }
496
+ result += processObject(rootObject, [schemaName], 1, schemaObj, pad);
457
497
  }
458
- });
459
- }
460
- // Handle the schema based on its type
461
- if (schemaObj.type === 'object' || schemaObj.properties) {
462
- // Create a root object for object schema
463
- const rootObject = {
464
- type: 'object',
465
- properties: schemaObj.properties || {},
466
- required: schemaObj.required || [],
467
- title: schemaObj.title,
468
- description: schemaObj.description,
469
- 'x-formData': schemaObj['x-formData'],
470
- };
471
- result += processObject(rootObject, [schemaName], 1, schemaObj, pad);
472
- } else if (['string', 'number', 'integer', 'boolean', 'null'].includes(schemaObj.type)) {
473
- // Handle primitive schema
474
- result += processPrimitive(schemaObj, schemaName, 1, pad);
475
- } else if (schemaObj.enum) {
476
- // Handle enum schema
477
- result += generateEnum(schemaObj, schemaName, 1, pad);
478
- } else if (schemaObj.anyOf || schemaObj.oneOf || schemaObj.allOf) {
479
- // Handle variant schema
480
- result += generateVariantEnum(schemaObj, schemaName, [schemaName], 1, schemaObj, pad);
481
- } else if (schemaObj.type === 'array') {
482
- // For array as root type, create a type alias to Vec<ItemType>
483
- let itemType = 'String'; // Default if no items specified
484
- if (schemaObj.items) {
485
- if (schemaObj.items.type === 'object' || schemaObj.items.properties) {
486
- // Create the item type
487
- const itemSchema = {
488
- ...schemaObj.items,
489
- title: schemaObj.items.title || `${schemaName}Item`,
490
- description: schemaObj.items.description || `Item of ${schemaName} array`,
491
- };
492
- result += processObject(itemSchema, [`${schemaName}Item`], 1, schemaObj, pad);
493
- itemType = `${schemaName}Item`;
494
- } else {
495
- // For primitive array items
496
- itemType = toRustType(schemaObj.items, [`${schemaName}Item`], schemaObj);
498
+ else if (['string', 'number', 'integer', 'boolean', 'null'].includes(schemaObj.type)) {
499
+ // Handle primitive schema
500
+ result += processPrimitive(schemaObj, schemaName, 1, pad);
497
501
  }
498
- }
499
- result += `${indentFn(1)}pub type ${schemaName} = Vec<${itemType}>;\n\n`;
500
- }
501
- });
502
- result += `${indentFn(0)}}\n`;
503
- return result;
502
+ else if (schemaObj.enum) {
503
+ // Handle enum schema
504
+ result += generateEnum(schemaObj, schemaName, 1, pad);
505
+ }
506
+ else if (schemaObj.anyOf || schemaObj.oneOf || schemaObj.allOf) {
507
+ // Handle variant schema
508
+ result += generateVariantEnum(schemaObj, schemaName, [schemaName], 1, schemaObj, pad);
509
+ }
510
+ else if (schemaObj.type === 'array') {
511
+ // For array as root type, create a type alias to Vec<ItemType>
512
+ let itemType = 'String'; // Default if no items specified
513
+ if (schemaObj.items) {
514
+ if (schemaObj.items.type === 'object' || schemaObj.items.properties) {
515
+ // Create the item type
516
+ const itemSchema = {
517
+ ...schemaObj.items,
518
+ title: schemaObj.items.title || `${schemaName}Item`,
519
+ description: schemaObj.items.description || `Item of ${schemaName} array`,
520
+ };
521
+ result += processObject(itemSchema, [`${schemaName}Item`], 1, schemaObj, pad);
522
+ itemType = `${schemaName}Item`;
523
+ }
524
+ else {
525
+ // For primitive array items
526
+ itemType = toRustType(schemaObj.items, [`${schemaName}Item`], schemaObj);
527
+ }
528
+ }
529
+ result += `${indentFn(1)}pub type ${schemaName} = Vec<${itemType}>;\n\n`;
530
+ }
531
+ });
532
+ result += `${indentFn(0)}}\n`;
533
+ return result;
504
534
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vovk-rust",
3
- "version": "0.0.1-draft.47",
3
+ "version": "0.0.1-draft.49",
4
4
  "description": "Vovk.ts Rust client",
5
5
  "scripts": {
6
6
  "build": "tsc",