@devlearning/swagger-generator 1.1.14 → 1.1.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/generator.js +55 -11
- package/dist/generators-writers/dart/api-dart-writer.js +32 -1
- package/dist/generators-writers/dart/model-dart-writer.js +15 -0
- package/dist/generators-writers/dart/templates/api.mustache +66 -1
- package/dist/generators-writers/dart/templates/model.mustache +3 -0
- package/dist/generators-writers/utils.d.ts +2 -2
- package/dist/generators-writers/utils.js +4 -2
- package/package.json +1 -1
- package/src/generator.ts +56 -11
- package/src/generators-writers/dart/api-dart-writer.ts +44 -1
- package/src/generators-writers/dart/model-dart-writer.ts +19 -0
- package/src/generators-writers/dart/templates/api.mustache +66 -1
- package/src/generators-writers/dart/templates/model.mustache +3 -0
- package/src/generators-writers/utils.ts +5 -2
package/dist/generator.js
CHANGED
|
@@ -74,7 +74,10 @@ export class Generator {
|
|
|
74
74
|
const apiSwaggerMethodKey = this._swagger.paths[apiName];
|
|
75
75
|
const apiMethod = Object.getOwnPropertyNames(apiSwaggerMethodKey)[0];
|
|
76
76
|
const apiSwaggerMethod = apiSwaggerMethodKey[apiMethod];
|
|
77
|
-
|
|
77
|
+
const multipartSchema = apiSwaggerMethod.requestBody?.content?.[contentTypeMultipartFormData]?.schema;
|
|
78
|
+
// Only create an ad-hoc request model when the multipart schema is inline.
|
|
79
|
+
// If the schema is a $ref, it's already part of components/schemas and will be generated there.
|
|
80
|
+
if (multipartSchema != null && multipartSchema.$ref == null) {
|
|
78
81
|
usedMultiPart.push(apiName);
|
|
79
82
|
}
|
|
80
83
|
}
|
|
@@ -114,6 +117,9 @@ export class Generator {
|
|
|
114
117
|
const method = Object.getOwnPropertyNames(swaggerMethod)[0];
|
|
115
118
|
const swaggerMethodInfo = swaggerMethod[method];
|
|
116
119
|
const schema = swaggerMethodInfo.requestBody.content[contentTypeMultipartFormData].schema;
|
|
120
|
+
if (schema?.$ref != null) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
117
123
|
this._models.push({
|
|
118
124
|
typeName: this.getApiNameNormalized(apiName),
|
|
119
125
|
modelType: 'class',
|
|
@@ -164,24 +170,58 @@ export class Generator {
|
|
|
164
170
|
return [];
|
|
165
171
|
let parameters = [];
|
|
166
172
|
try {
|
|
173
|
+
// Path params are always explicit parameters.
|
|
174
|
+
swaggerMethod.parameters?.filter(x => x.in == 'path').forEach(parameter => {
|
|
175
|
+
const name = this.toFirstLetterLowercase(parameter.name);
|
|
176
|
+
if (parameter.schema?.$ref != null) {
|
|
177
|
+
const type = this.retrieveType(parameter.schema);
|
|
178
|
+
parameters.push({
|
|
179
|
+
name,
|
|
180
|
+
typeName: type.typeName,
|
|
181
|
+
nullable: false,
|
|
182
|
+
swaggerParameter: parameter,
|
|
183
|
+
isQuery: false,
|
|
184
|
+
isEnum: this.isEnum(parameter.schema.$ref),
|
|
185
|
+
isTypeReference: false,
|
|
186
|
+
isNativeType: false,
|
|
187
|
+
isArray: false,
|
|
188
|
+
isVoid: false,
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
parameters.push({
|
|
193
|
+
name,
|
|
194
|
+
typeName: this.getNativeType(parameter.schema),
|
|
195
|
+
nullable: false,
|
|
196
|
+
swaggerParameter: parameter,
|
|
197
|
+
isQuery: false,
|
|
198
|
+
isEnum: false,
|
|
199
|
+
isTypeReference: false,
|
|
200
|
+
isNativeType: true,
|
|
201
|
+
isArray: false,
|
|
202
|
+
isVoid: false,
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
});
|
|
167
206
|
if (swaggerMethod.requestBody != null && swaggerMethod.requestBody.content[contentTypeMultipartFormData] != null) {
|
|
168
|
-
|
|
169
|
-
|
|
207
|
+
const schema = swaggerMethod.requestBody.content[contentTypeMultipartFormData].schema;
|
|
208
|
+
const type = this.retrieveType(schema);
|
|
209
|
+
parameters.unshift({
|
|
170
210
|
name: 'request',
|
|
171
|
-
typeName:
|
|
211
|
+
typeName: type.typeName,
|
|
172
212
|
nullable: false,
|
|
173
213
|
isQuery: false,
|
|
174
214
|
isEnum: false,
|
|
175
|
-
isTypeReference: true,
|
|
176
|
-
isNativeType: false,
|
|
177
|
-
isArray: false,
|
|
178
|
-
isVoid: false,
|
|
215
|
+
isTypeReference: type.isTypeReference ?? true,
|
|
216
|
+
isNativeType: type.isNativeType ?? false,
|
|
217
|
+
isArray: type.isArray ?? false,
|
|
218
|
+
isVoid: type.isVoid ?? false,
|
|
179
219
|
});
|
|
180
220
|
}
|
|
181
221
|
else {
|
|
182
222
|
if (swaggerMethod.requestBody != null) {
|
|
183
223
|
const type = this.retrieveType(swaggerMethod.requestBody.content[contentTypeApplicationJson].schema);
|
|
184
|
-
parameters.
|
|
224
|
+
parameters.unshift({
|
|
185
225
|
name: 'request',
|
|
186
226
|
typeName: type.typeName,
|
|
187
227
|
nullable: false,
|
|
@@ -327,12 +367,16 @@ export class Generator {
|
|
|
327
367
|
};
|
|
328
368
|
}
|
|
329
369
|
else {
|
|
370
|
+
// Array of native types (including binary/file)
|
|
371
|
+
const itemType = swaggerComponentProperty.items
|
|
372
|
+
? this.getNativeType(swaggerComponentProperty.items)
|
|
373
|
+
: this.getNativeType(swaggerComponentProperty);
|
|
330
374
|
return {
|
|
331
|
-
typeName:
|
|
375
|
+
typeName: itemType,
|
|
332
376
|
isTypeReference: false,
|
|
333
377
|
isNativeType: true,
|
|
334
378
|
nullable: swaggerComponentProperty.nullable ?? false,
|
|
335
|
-
isArray:
|
|
379
|
+
isArray: true,
|
|
336
380
|
isVoid: false,
|
|
337
381
|
};
|
|
338
382
|
}
|
|
@@ -105,17 +105,32 @@ export class ApiDartWriter {
|
|
|
105
105
|
if (methodName.toLowerCase().indexOf("productsave") >= 0) {
|
|
106
106
|
debugger;
|
|
107
107
|
}
|
|
108
|
+
const pathParams = (api.parameters ?? [])
|
|
109
|
+
.filter(p => p.name !== 'request' && p.isQuery === false)
|
|
110
|
+
.map(p => ({
|
|
111
|
+
name: p.name,
|
|
112
|
+
type: p.typeName ? Normalizator.mapTsTypeToDart(p.typeName) : 'String',
|
|
113
|
+
nullable: false,
|
|
114
|
+
}));
|
|
115
|
+
// Build a Dart-interpolated path string (e.g. '/x/{id}' -> '/x/$id')
|
|
116
|
+
const pathExpression = api.url.replace(/\{([^}]+)\}/g, (_, rawName) => {
|
|
117
|
+
const varName = Utils.toFirstLetterLowercase(rawName);
|
|
118
|
+
return `\$${varName}`;
|
|
119
|
+
});
|
|
108
120
|
const endpoint = {
|
|
109
121
|
methodName: methodName,
|
|
110
122
|
httpMethod: api.method.toLowerCase(),
|
|
111
|
-
path:
|
|
123
|
+
path: pathExpression,
|
|
112
124
|
responseType: responseType,
|
|
113
125
|
isResponseNativeType: api.returnType ? api.returnType.isNativeType : true,
|
|
114
126
|
haveRequest: api.haveRequest,
|
|
115
127
|
requestType: requestType,
|
|
128
|
+
pathParams,
|
|
129
|
+
isMultiPart: api.isMultiPart === true,
|
|
116
130
|
};
|
|
117
131
|
if (api.parameters && api.parameters.length > 0 && !api.haveRequest) {
|
|
118
132
|
endpoint.queryParams = api.parameters
|
|
133
|
+
.filter(p => p.isQuery === true)
|
|
119
134
|
.map(p => ({
|
|
120
135
|
name: p.name,
|
|
121
136
|
type: p.typeName ? Normalizator.mapTsTypeToDart(p.typeName) : 'String',
|
|
@@ -136,6 +151,22 @@ export class ApiDartWriter {
|
|
|
136
151
|
});
|
|
137
152
|
}
|
|
138
153
|
}
|
|
154
|
+
// Multipart: include request model fields so template can build FormData
|
|
155
|
+
if (endpoint.isMultiPart && api.haveRequest && api.parameters[0]?.typeName) {
|
|
156
|
+
const requestModelTypeName = api.parameters[0].typeName;
|
|
157
|
+
const requestModel = models.find(m => m.typeName === requestModelTypeName);
|
|
158
|
+
if (requestModel) {
|
|
159
|
+
endpoint.multipartFields = requestModel.properties.map(p => ({
|
|
160
|
+
name: p.name,
|
|
161
|
+
isFile: p.typeName === 'File',
|
|
162
|
+
isArray: p.isArray === true,
|
|
163
|
+
nullable: p.nullable === true,
|
|
164
|
+
}));
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
endpoint.multipartFields = [];
|
|
168
|
+
}
|
|
169
|
+
}
|
|
139
170
|
if (api.returnType && !api.returnType.isNativeType) {
|
|
140
171
|
if (imports.findIndex(x => x.type.typeName == api.returnType.typeName) == -1) {
|
|
141
172
|
models.forEach(model => {
|
|
@@ -33,15 +33,30 @@ export class ModelDartWriter {
|
|
|
33
33
|
imports: [],
|
|
34
34
|
};
|
|
35
35
|
var imports = [];
|
|
36
|
+
const needsDartIo = model.properties.some(p => {
|
|
37
|
+
const normalized = (p.typeName ?? '').trim();
|
|
38
|
+
return normalized === 'File' || (p.isArray && normalized === 'File');
|
|
39
|
+
});
|
|
40
|
+
if (needsDartIo) {
|
|
41
|
+
imports.push({
|
|
42
|
+
type: { typeName: 'dart:io' },
|
|
43
|
+
import: `import 'dart:io';`
|
|
44
|
+
});
|
|
45
|
+
}
|
|
36
46
|
model.properties.forEach(property => {
|
|
37
47
|
var fieldTypeName = Normalizator.mapTsTypeToDart(property.typeName);
|
|
38
48
|
fieldTypeName = Normalizator.getNormalizedTypeName(fieldTypeName);
|
|
49
|
+
const isFileField = property.typeName === 'File';
|
|
50
|
+
const jsonKeyAnnotation = isFileField
|
|
51
|
+
? "@JsonKey(includeFromJson: false, includeToJson: false)"
|
|
52
|
+
: undefined;
|
|
39
53
|
dartModel.fields.push({
|
|
40
54
|
name: property.name,
|
|
41
55
|
type: property.isArray ? `List<${fieldTypeName}>` : fieldTypeName,
|
|
42
56
|
typeName: property.typeName,
|
|
43
57
|
nullable: property.nullable && !property.isArray ? '?' : '',
|
|
44
58
|
required: property.nullable && !property.isArray ? '' : 'required ',
|
|
59
|
+
jsonKeyAnnotation,
|
|
45
60
|
});
|
|
46
61
|
if (property.isTypeReference) {
|
|
47
62
|
if (imports.findIndex(x => x.type.typeName == property.typeName) == -1) {
|
|
@@ -11,15 +11,80 @@ class {{apiClassName}} {
|
|
|
11
11
|
|
|
12
12
|
{{#endpoints}}
|
|
13
13
|
Future<{{responseType}}> {{methodName}}(
|
|
14
|
-
{{#haveRequest}}{{requestType}} request{{/haveRequest}}
|
|
14
|
+
{{#haveRequest}}{{requestType}} request,{{/haveRequest}}
|
|
15
|
+
{{#pathParams}}
|
|
16
|
+
{{type}}{{#nullable}}?{{/nullable}} {{name}},
|
|
17
|
+
{{/pathParams}}
|
|
15
18
|
{{#queryParams}}
|
|
16
19
|
{{type}}{{#nullable}}?{{/nullable}} {{name}},
|
|
17
20
|
{{/queryParams}}
|
|
18
21
|
) async {
|
|
22
|
+
{{#isMultiPart}}
|
|
23
|
+
final formData = FormData();
|
|
24
|
+
{{#multipartFields}}
|
|
25
|
+
{{#isFile}}
|
|
26
|
+
{{#isArray}}
|
|
27
|
+
{{^nullable}}
|
|
28
|
+
for (final file in request.{{name}}) {
|
|
29
|
+
formData.files.add(MapEntry('{{name}}', await MultipartFile.fromFile(file.path)));
|
|
30
|
+
}
|
|
31
|
+
{{/nullable}}
|
|
32
|
+
{{#nullable}}
|
|
33
|
+
if (request.{{name}} != null) {
|
|
34
|
+
for (final file in request.{{name}}!) {
|
|
35
|
+
formData.files.add(MapEntry('{{name}}', await MultipartFile.fromFile(file.path)));
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
{{/nullable}}
|
|
39
|
+
{{/isArray}}
|
|
40
|
+
{{^isArray}}
|
|
41
|
+
{{^nullable}}
|
|
42
|
+
formData.files.add(MapEntry('{{name}}', await MultipartFile.fromFile(request.{{name}}.path)));
|
|
43
|
+
{{/nullable}}
|
|
44
|
+
{{#nullable}}
|
|
45
|
+
if (request.{{name}} != null) {
|
|
46
|
+
formData.files.add(MapEntry('{{name}}', await MultipartFile.fromFile(request.{{name}}!.path)));
|
|
47
|
+
}
|
|
48
|
+
{{/nullable}}
|
|
49
|
+
{{/isArray}}
|
|
50
|
+
{{/isFile}}
|
|
51
|
+
{{^isFile}}
|
|
52
|
+
{{#isArray}}
|
|
53
|
+
{{^nullable}}
|
|
54
|
+
for (final value in request.{{name}}) {
|
|
55
|
+
formData.fields.add(MapEntry('{{name}}', value.toString()));
|
|
56
|
+
}
|
|
57
|
+
{{/nullable}}
|
|
58
|
+
{{#nullable}}
|
|
59
|
+
if (request.{{name}} != null) {
|
|
60
|
+
for (final value in request.{{name}}!) {
|
|
61
|
+
formData.fields.add(MapEntry('{{name}}', value.toString()));
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
{{/nullable}}
|
|
65
|
+
{{/isArray}}
|
|
66
|
+
{{^isArray}}
|
|
67
|
+
{{^nullable}}
|
|
68
|
+
formData.fields.add(MapEntry('{{name}}', request.{{name}}.toString()));
|
|
69
|
+
{{/nullable}}
|
|
70
|
+
{{#nullable}}
|
|
71
|
+
if (request.{{name}} != null) {
|
|
72
|
+
formData.fields.add(MapEntry('{{name}}', request.{{name}}!.toString()));
|
|
73
|
+
}
|
|
74
|
+
{{/nullable}}
|
|
75
|
+
{{/isArray}}
|
|
76
|
+
{{/isFile}}
|
|
77
|
+
{{/multipartFields}}
|
|
78
|
+
{{/isMultiPart}}
|
|
19
79
|
final response = await _dio.{{httpMethod}}(
|
|
20
80
|
'{{{path}}}',
|
|
21
81
|
{{#haveRequest}}
|
|
82
|
+
{{#isMultiPart}}
|
|
83
|
+
data: formData,
|
|
84
|
+
{{/isMultiPart}}
|
|
85
|
+
{{^isMultiPart}}
|
|
22
86
|
data: request.toJson(),
|
|
87
|
+
{{/isMultiPart}}
|
|
23
88
|
{{/haveRequest}}
|
|
24
89
|
{{^haveRequest}}
|
|
25
90
|
queryParameters: {
|
|
@@ -10,6 +10,9 @@ part '{{filename}}.g.dart';
|
|
|
10
10
|
abstract class {{className}} with _${{className}} {
|
|
11
11
|
const factory {{className}}({
|
|
12
12
|
{{#fields}}
|
|
13
|
+
{{#jsonKeyAnnotation}}
|
|
14
|
+
{{{jsonKeyAnnotation}}}
|
|
15
|
+
{{/jsonKeyAnnotation}}
|
|
13
16
|
{{required}}{{{type}}}{{nullable}} {{name}},
|
|
14
17
|
{{/fields}}
|
|
15
18
|
}) = _{{className}};
|
|
@@ -8,6 +8,6 @@ export declare class Utils {
|
|
|
8
8
|
static isDate(schema?: SwaggerSchema): boolean;
|
|
9
9
|
static toDartFileName(name: string): string;
|
|
10
10
|
static toDartClassName(name: string): string | undefined;
|
|
11
|
-
static ensureDirectorySync(dirPath: string):
|
|
12
|
-
static clearDirectory(dirPath: string):
|
|
11
|
+
static ensureDirectorySync(dirPath: string): void;
|
|
12
|
+
static clearDirectory(dirPath: string): void;
|
|
13
13
|
}
|
|
@@ -10,6 +10,8 @@ export class Utils {
|
|
|
10
10
|
return normalizedApiName;
|
|
11
11
|
}
|
|
12
12
|
static getNormalizedApiPathDart(apiName) {
|
|
13
|
+
// Remove path params like {id} from the method name.
|
|
14
|
+
apiName = apiName.replace(/\{[^}]+\}/g, '');
|
|
13
15
|
let normalizedApiName = apiName.replace('/api/v{version}/', '').replaceAll('/', '_');
|
|
14
16
|
if (normalizedApiName.charAt(0) == '_') {
|
|
15
17
|
normalizedApiName = normalizedApiName.slice(1);
|
|
@@ -47,12 +49,12 @@ export class Utils {
|
|
|
47
49
|
return undefined;
|
|
48
50
|
return name.replace(/\./g, ''); // rimuove i punti per ottenere PascalCase
|
|
49
51
|
}
|
|
50
|
-
static
|
|
52
|
+
static ensureDirectorySync(dirPath) {
|
|
51
53
|
if (!fs.existsSync(dirPath)) {
|
|
52
54
|
fs.mkdirSync(dirPath, { recursive: true });
|
|
53
55
|
}
|
|
54
56
|
}
|
|
55
|
-
static
|
|
57
|
+
static clearDirectory(dirPath) {
|
|
56
58
|
if (!fs.existsSync(dirPath))
|
|
57
59
|
return;
|
|
58
60
|
for (const file of fs.readdirSync(dirPath)) {
|
package/package.json
CHANGED
package/src/generator.ts
CHANGED
|
@@ -103,7 +103,10 @@ export class Generator {
|
|
|
103
103
|
const apiMethod = Object.getOwnPropertyNames(apiSwaggerMethodKey)[0];
|
|
104
104
|
const apiSwaggerMethod = apiSwaggerMethodKey[apiMethod];
|
|
105
105
|
|
|
106
|
-
|
|
106
|
+
const multipartSchema = apiSwaggerMethod.requestBody?.content?.[contentTypeMultipartFormData]?.schema;
|
|
107
|
+
// Only create an ad-hoc request model when the multipart schema is inline.
|
|
108
|
+
// If the schema is a $ref, it's already part of components/schemas and will be generated there.
|
|
109
|
+
if (multipartSchema != null && multipartSchema.$ref == null) {
|
|
107
110
|
usedMultiPart.push(apiName);
|
|
108
111
|
}
|
|
109
112
|
}
|
|
@@ -154,6 +157,10 @@ export class Generator {
|
|
|
154
157
|
const swaggerMethodInfo = swaggerMethod[method];
|
|
155
158
|
const schema = swaggerMethodInfo.requestBody.content[contentTypeMultipartFormData].schema;
|
|
156
159
|
|
|
160
|
+
if (schema?.$ref != null) {
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
|
|
157
164
|
this._models.push({
|
|
158
165
|
typeName: this.getApiNameNormalized(apiName),
|
|
159
166
|
modelType: 'class',
|
|
@@ -209,23 +216,57 @@ export class Generator {
|
|
|
209
216
|
|
|
210
217
|
let parameters: ParameterDto[] = [];
|
|
211
218
|
try {
|
|
219
|
+
// Path params are always explicit parameters.
|
|
220
|
+
swaggerMethod.parameters?.filter(x => x.in == 'path').forEach(parameter => {
|
|
221
|
+
const name = this.toFirstLetterLowercase(parameter.name);
|
|
222
|
+
if (parameter.schema?.$ref != null) {
|
|
223
|
+
const type = this.retrieveType(parameter.schema);
|
|
224
|
+
parameters.push({
|
|
225
|
+
name,
|
|
226
|
+
typeName: type.typeName,
|
|
227
|
+
nullable: false,
|
|
228
|
+
swaggerParameter: parameter,
|
|
229
|
+
isQuery: false,
|
|
230
|
+
isEnum: this.isEnum(parameter.schema.$ref),
|
|
231
|
+
isTypeReference: false,
|
|
232
|
+
isNativeType: false,
|
|
233
|
+
isArray: false,
|
|
234
|
+
isVoid: false,
|
|
235
|
+
});
|
|
236
|
+
} else {
|
|
237
|
+
parameters.push({
|
|
238
|
+
name,
|
|
239
|
+
typeName: this.getNativeType(parameter.schema),
|
|
240
|
+
nullable: false,
|
|
241
|
+
swaggerParameter: parameter,
|
|
242
|
+
isQuery: false,
|
|
243
|
+
isEnum: false,
|
|
244
|
+
isTypeReference: false,
|
|
245
|
+
isNativeType: true,
|
|
246
|
+
isArray: false,
|
|
247
|
+
isVoid: false,
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
|
|
212
252
|
if (swaggerMethod.requestBody != null && swaggerMethod.requestBody.content[contentTypeMultipartFormData] != null) {
|
|
213
|
-
|
|
214
|
-
|
|
253
|
+
const schema = swaggerMethod.requestBody.content[contentTypeMultipartFormData].schema;
|
|
254
|
+
const type = this.retrieveType(schema);
|
|
255
|
+
parameters.unshift({
|
|
215
256
|
name: 'request',
|
|
216
|
-
typeName:
|
|
257
|
+
typeName: type.typeName,
|
|
217
258
|
nullable: false,
|
|
218
259
|
isQuery: false,
|
|
219
260
|
isEnum: false,
|
|
220
|
-
isTypeReference: true,
|
|
221
|
-
isNativeType: false,
|
|
222
|
-
isArray: false,
|
|
223
|
-
isVoid: false,
|
|
261
|
+
isTypeReference: type.isTypeReference ?? true,
|
|
262
|
+
isNativeType: type.isNativeType ?? false,
|
|
263
|
+
isArray: type.isArray ?? false,
|
|
264
|
+
isVoid: type.isVoid ?? false,
|
|
224
265
|
});
|
|
225
266
|
} else {
|
|
226
267
|
if (swaggerMethod.requestBody != null) {
|
|
227
268
|
const type = this.retrieveType(swaggerMethod.requestBody.content[contentTypeApplicationJson].schema);
|
|
228
|
-
parameters.
|
|
269
|
+
parameters.unshift({
|
|
229
270
|
name: 'request',
|
|
230
271
|
typeName: type.typeName, //swaggerMethod.requestBody.content[contentTypeApplicationJson].schema.$ref.replace('#/components/schemas/', ''),
|
|
231
272
|
nullable: false,
|
|
@@ -376,12 +417,16 @@ export class Generator {
|
|
|
376
417
|
};
|
|
377
418
|
}
|
|
378
419
|
else {
|
|
420
|
+
// Array of native types (including binary/file)
|
|
421
|
+
const itemType = swaggerComponentProperty.items
|
|
422
|
+
? this.getNativeType(swaggerComponentProperty.items)
|
|
423
|
+
: this.getNativeType(swaggerComponentProperty);
|
|
379
424
|
return {
|
|
380
|
-
typeName:
|
|
425
|
+
typeName: itemType,
|
|
381
426
|
isTypeReference: false,
|
|
382
427
|
isNativeType: true,
|
|
383
428
|
nullable: swaggerComponentProperty.nullable ?? false,
|
|
384
|
-
isArray:
|
|
429
|
+
isArray: true,
|
|
385
430
|
isVoid: false,
|
|
386
431
|
};
|
|
387
432
|
}
|
|
@@ -30,6 +30,16 @@ interface EndpointDefinitionDart {
|
|
|
30
30
|
haveRequest: boolean;
|
|
31
31
|
requestType?: string; // solo se haveRequest è true
|
|
32
32
|
queryParams?: Parameter[],
|
|
33
|
+
pathParams?: Parameter[],
|
|
34
|
+
isMultiPart: boolean;
|
|
35
|
+
multipartFields?: MultipartFieldDefinitionDart[];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface MultipartFieldDefinitionDart {
|
|
39
|
+
name: string;
|
|
40
|
+
isFile: boolean;
|
|
41
|
+
isArray: boolean;
|
|
42
|
+
nullable: boolean;
|
|
33
43
|
}
|
|
34
44
|
|
|
35
45
|
interface Parameter {
|
|
@@ -167,18 +177,35 @@ export class ApiDartWriter {
|
|
|
167
177
|
debugger
|
|
168
178
|
}
|
|
169
179
|
|
|
180
|
+
const pathParams: Parameter[] = (api.parameters ?? [])
|
|
181
|
+
.filter(p => p.name !== 'request' && p.isQuery === false)
|
|
182
|
+
.map(p => ({
|
|
183
|
+
name: p.name,
|
|
184
|
+
type: p.typeName ? Normalizator.mapTsTypeToDart(p.typeName)! : 'String',
|
|
185
|
+
nullable: false,
|
|
186
|
+
}));
|
|
187
|
+
|
|
188
|
+
// Build a Dart-interpolated path string (e.g. '/x/{id}' -> '/x/$id')
|
|
189
|
+
const pathExpression = api.url.replace(/\{([^}]+)\}/g, (_, rawName) => {
|
|
190
|
+
const varName = Utils.toFirstLetterLowercase(rawName);
|
|
191
|
+
return `\$${varName}`;
|
|
192
|
+
});
|
|
193
|
+
|
|
170
194
|
const endpoint = <EndpointDefinitionDart>{
|
|
171
195
|
methodName: methodName,
|
|
172
196
|
httpMethod: api.method.toLowerCase() as HttpMethodDart,
|
|
173
|
-
path:
|
|
197
|
+
path: pathExpression,
|
|
174
198
|
responseType: responseType,
|
|
175
199
|
isResponseNativeType: api.returnType ? api.returnType.isNativeType : true,
|
|
176
200
|
haveRequest: api.haveRequest,
|
|
177
201
|
requestType: requestType,
|
|
202
|
+
pathParams,
|
|
203
|
+
isMultiPart: api.isMultiPart === true,
|
|
178
204
|
};
|
|
179
205
|
|
|
180
206
|
if (api.parameters && api.parameters.length > 0 && !api.haveRequest) {
|
|
181
207
|
endpoint.queryParams = api.parameters
|
|
208
|
+
.filter(p => p.isQuery === true)
|
|
182
209
|
.map(p => ({
|
|
183
210
|
name: p.name,
|
|
184
211
|
type: p.typeName ? Normalizator.mapTsTypeToDart(p.typeName)! : 'String',
|
|
@@ -201,6 +228,22 @@ export class ApiDartWriter {
|
|
|
201
228
|
}
|
|
202
229
|
}
|
|
203
230
|
|
|
231
|
+
// Multipart: include request model fields so template can build FormData
|
|
232
|
+
if (endpoint.isMultiPart && api.haveRequest && api.parameters[0]?.typeName) {
|
|
233
|
+
const requestModelTypeName = api.parameters[0].typeName;
|
|
234
|
+
const requestModel = models.find(m => m.typeName === requestModelTypeName);
|
|
235
|
+
if (requestModel) {
|
|
236
|
+
endpoint.multipartFields = requestModel.properties.map(p => ({
|
|
237
|
+
name: p.name,
|
|
238
|
+
isFile: p.typeName === 'File',
|
|
239
|
+
isArray: p.isArray === true,
|
|
240
|
+
nullable: p.nullable === true,
|
|
241
|
+
}));
|
|
242
|
+
} else {
|
|
243
|
+
endpoint.multipartFields = [];
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
204
247
|
if (api.returnType && !api.returnType.isNativeType) {
|
|
205
248
|
if (imports.findIndex(x => x.type.typeName == api.returnType!.typeName) == -1) {
|
|
206
249
|
models.forEach(model => {
|
|
@@ -32,6 +32,7 @@ interface FieldDefinitionDart {
|
|
|
32
32
|
typeName: string;
|
|
33
33
|
nullable: string;
|
|
34
34
|
required: string;
|
|
35
|
+
jsonKeyAnnotation?: string;
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
interface ExportClassDefinitionDart {
|
|
@@ -82,17 +83,35 @@ export class ModelDartWriter {
|
|
|
82
83
|
|
|
83
84
|
var imports = <ImportDefinitionDart[]>[];
|
|
84
85
|
|
|
86
|
+
const needsDartIo = model.properties.some(p => {
|
|
87
|
+
const normalized = (p.typeName ?? '').trim();
|
|
88
|
+
return normalized === 'File' || (p.isArray && normalized === 'File');
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
if (needsDartIo) {
|
|
92
|
+
imports.push({
|
|
93
|
+
type: { typeName: 'dart:io' } as any,
|
|
94
|
+
import: `import 'dart:io';`
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
85
98
|
model.properties.forEach(property => {
|
|
86
99
|
var fieldTypeName = Normalizator.mapTsTypeToDart(property.typeName);
|
|
87
100
|
|
|
88
101
|
fieldTypeName = Normalizator.getNormalizedTypeName(fieldTypeName);
|
|
89
102
|
|
|
103
|
+
const isFileField = property.typeName === 'File';
|
|
104
|
+
const jsonKeyAnnotation = isFileField
|
|
105
|
+
? "@JsonKey(includeFromJson: false, includeToJson: false)"
|
|
106
|
+
: undefined;
|
|
107
|
+
|
|
90
108
|
dartModel.fields!.push(<FieldDefinitionDart>{
|
|
91
109
|
name: property.name,
|
|
92
110
|
type: property.isArray ? `List<${fieldTypeName}>` : fieldTypeName,
|
|
93
111
|
typeName: property.typeName,
|
|
94
112
|
nullable: property.nullable && !property.isArray ? '?' : '',
|
|
95
113
|
required: property.nullable && !property.isArray ? '' : 'required ',
|
|
114
|
+
jsonKeyAnnotation,
|
|
96
115
|
});
|
|
97
116
|
|
|
98
117
|
if (property.isTypeReference) {
|
|
@@ -11,15 +11,80 @@ class {{apiClassName}} {
|
|
|
11
11
|
|
|
12
12
|
{{#endpoints}}
|
|
13
13
|
Future<{{responseType}}> {{methodName}}(
|
|
14
|
-
{{#haveRequest}}{{requestType}} request{{/haveRequest}}
|
|
14
|
+
{{#haveRequest}}{{requestType}} request,{{/haveRequest}}
|
|
15
|
+
{{#pathParams}}
|
|
16
|
+
{{type}}{{#nullable}}?{{/nullable}} {{name}},
|
|
17
|
+
{{/pathParams}}
|
|
15
18
|
{{#queryParams}}
|
|
16
19
|
{{type}}{{#nullable}}?{{/nullable}} {{name}},
|
|
17
20
|
{{/queryParams}}
|
|
18
21
|
) async {
|
|
22
|
+
{{#isMultiPart}}
|
|
23
|
+
final formData = FormData();
|
|
24
|
+
{{#multipartFields}}
|
|
25
|
+
{{#isFile}}
|
|
26
|
+
{{#isArray}}
|
|
27
|
+
{{^nullable}}
|
|
28
|
+
for (final file in request.{{name}}) {
|
|
29
|
+
formData.files.add(MapEntry('{{name}}', await MultipartFile.fromFile(file.path)));
|
|
30
|
+
}
|
|
31
|
+
{{/nullable}}
|
|
32
|
+
{{#nullable}}
|
|
33
|
+
if (request.{{name}} != null) {
|
|
34
|
+
for (final file in request.{{name}}!) {
|
|
35
|
+
formData.files.add(MapEntry('{{name}}', await MultipartFile.fromFile(file.path)));
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
{{/nullable}}
|
|
39
|
+
{{/isArray}}
|
|
40
|
+
{{^isArray}}
|
|
41
|
+
{{^nullable}}
|
|
42
|
+
formData.files.add(MapEntry('{{name}}', await MultipartFile.fromFile(request.{{name}}.path)));
|
|
43
|
+
{{/nullable}}
|
|
44
|
+
{{#nullable}}
|
|
45
|
+
if (request.{{name}} != null) {
|
|
46
|
+
formData.files.add(MapEntry('{{name}}', await MultipartFile.fromFile(request.{{name}}!.path)));
|
|
47
|
+
}
|
|
48
|
+
{{/nullable}}
|
|
49
|
+
{{/isArray}}
|
|
50
|
+
{{/isFile}}
|
|
51
|
+
{{^isFile}}
|
|
52
|
+
{{#isArray}}
|
|
53
|
+
{{^nullable}}
|
|
54
|
+
for (final value in request.{{name}}) {
|
|
55
|
+
formData.fields.add(MapEntry('{{name}}', value.toString()));
|
|
56
|
+
}
|
|
57
|
+
{{/nullable}}
|
|
58
|
+
{{#nullable}}
|
|
59
|
+
if (request.{{name}} != null) {
|
|
60
|
+
for (final value in request.{{name}}!) {
|
|
61
|
+
formData.fields.add(MapEntry('{{name}}', value.toString()));
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
{{/nullable}}
|
|
65
|
+
{{/isArray}}
|
|
66
|
+
{{^isArray}}
|
|
67
|
+
{{^nullable}}
|
|
68
|
+
formData.fields.add(MapEntry('{{name}}', request.{{name}}.toString()));
|
|
69
|
+
{{/nullable}}
|
|
70
|
+
{{#nullable}}
|
|
71
|
+
if (request.{{name}} != null) {
|
|
72
|
+
formData.fields.add(MapEntry('{{name}}', request.{{name}}!.toString()));
|
|
73
|
+
}
|
|
74
|
+
{{/nullable}}
|
|
75
|
+
{{/isArray}}
|
|
76
|
+
{{/isFile}}
|
|
77
|
+
{{/multipartFields}}
|
|
78
|
+
{{/isMultiPart}}
|
|
19
79
|
final response = await _dio.{{httpMethod}}(
|
|
20
80
|
'{{{path}}}',
|
|
21
81
|
{{#haveRequest}}
|
|
82
|
+
{{#isMultiPart}}
|
|
83
|
+
data: formData,
|
|
84
|
+
{{/isMultiPart}}
|
|
85
|
+
{{^isMultiPart}}
|
|
22
86
|
data: request.toJson(),
|
|
87
|
+
{{/isMultiPart}}
|
|
23
88
|
{{/haveRequest}}
|
|
24
89
|
{{^haveRequest}}
|
|
25
90
|
queryParameters: {
|
|
@@ -10,6 +10,9 @@ part '{{filename}}.g.dart';
|
|
|
10
10
|
abstract class {{className}} with _${{className}} {
|
|
11
11
|
const factory {{className}}({
|
|
12
12
|
{{#fields}}
|
|
13
|
+
{{#jsonKeyAnnotation}}
|
|
14
|
+
{{{jsonKeyAnnotation}}}
|
|
15
|
+
{{/jsonKeyAnnotation}}
|
|
13
16
|
{{required}}{{{type}}}{{nullable}} {{name}},
|
|
14
17
|
{{/fields}}
|
|
15
18
|
}) = _{{className}};
|
|
@@ -17,6 +17,9 @@ export class Utils {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
public static getNormalizedApiPathDart(apiName: string) {
|
|
20
|
+
// Remove path params like {id} from the method name.
|
|
21
|
+
apiName = apiName.replace(/\{[^}]+\}/g, '');
|
|
22
|
+
|
|
20
23
|
let normalizedApiName = apiName.replace('/api/v{version}/', '').replaceAll('/', '_');
|
|
21
24
|
|
|
22
25
|
if (normalizedApiName.charAt(0) == '_') {
|
|
@@ -67,13 +70,13 @@ export class Utils {
|
|
|
67
70
|
return name.replace(/\./g, ''); // rimuove i punti per ottenere PascalCase
|
|
68
71
|
}
|
|
69
72
|
|
|
70
|
-
public static
|
|
73
|
+
public static ensureDirectorySync(dirPath: string) {
|
|
71
74
|
if (!fs.existsSync(dirPath)) {
|
|
72
75
|
fs.mkdirSync(dirPath, { recursive: true });
|
|
73
76
|
}
|
|
74
77
|
}
|
|
75
78
|
|
|
76
|
-
public static
|
|
79
|
+
public static clearDirectory(dirPath: string) {
|
|
77
80
|
if (!fs.existsSync(dirPath)) return;
|
|
78
81
|
|
|
79
82
|
for (const file of fs.readdirSync(dirPath)) {
|