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.
- package/client-templates/rsSrc/http_request.rs +22 -4
- package/client-templates/rsSrc/lib.rs.ejs +18 -4
- package/index.js +561 -449
- package/package.json +1 -1
|
@@ -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: <%-
|
|
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
|
-
|
|
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
|
-
<%-
|
|
69
|
-
<%-
|
|
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(¶ms),
|
|
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
|
-
|
|
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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
-
|
|
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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
if (schema.type === '
|
|
159
|
-
|
|
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
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
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
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
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
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
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
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
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
|
-
|
|
261
|
-
|
|
262
|
-
|
|
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
|
-
//
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
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 (!
|
|
322
|
-
|
|
347
|
+
if (!schema.properties) {
|
|
348
|
+
return '';
|
|
323
349
|
}
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
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
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
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
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
code +=
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
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
|
-
|
|
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
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
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
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
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
|
-
|
|
498
|
-
|
|
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
|
}
|