vovk-rust 0.0.1-beta.15 → 0.0.1-beta.16

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