diff --git a/.circleci/config.yml b/.circleci/config.yml index c5b0d3dc6f1..c3611c57351 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -82,7 +82,6 @@ jobs: - ~/.bundle - ~/.go_workspace - ~/.gradle - - ~/.pub-cache - ~/.cache/bower - ".git" - ~/.stack diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/DartDioNextClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/DartDioNextClientCodegen.java index 3beb4ca9a4a..6b7beaeaea2 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/DartDioNextClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/DartDioNextClientCodegen.java @@ -46,6 +46,7 @@ public class DartDioNextClientCodegen extends AbstractDartCodegen { public static final String SERIALIZATION_LIBRARY_BUILT_VALUE = "built_value"; public static final String SERIALIZATION_LIBRARY_DEFAULT = SERIALIZATION_LIBRARY_BUILT_VALUE; + private static final String DIO_IMPORT = "package:dio/dio.dart"; private static final String CLIENT_NAME = "clientName"; private String dateLibrary; @@ -192,6 +193,7 @@ public class DartDioNextClientCodegen extends AbstractDartCodegen { imports.put("BuiltMap", "package:built_collection/built_collection.dart"); imports.put("JsonObject", "package:built_value/json_object.dart"); imports.put("Uint8List", "dart:typed_data"); + imports.put("MultipartFile", DIO_IMPORT); } private void configureDateLibrary(String srcFolder) { @@ -257,7 +259,7 @@ public class DartDioNextClientCodegen extends AbstractDartCodegen { for (Object _mo : models) { Map mo = (Map) _mo; CodegenModel cm = (CodegenModel) mo.get("model"); - cm.imports = rewriteImports(cm.imports); + cm.imports = rewriteImports(cm.imports, true); cm.vendorExtensions.put("x-has-vars", !cm.vars.isEmpty()); } return objs; @@ -302,7 +304,6 @@ public class DartDioNextClientCodegen extends AbstractDartCodegen { sb.append(")]"); } - @Override public Map postProcessOperationsWithModels(Map objs, List allModels) { super.postProcessOperationsWithModels(objs, allModels); @@ -313,11 +314,15 @@ public class DartDioNextClientCodegen extends AbstractDartCodegen { Set resultImports = new HashSet<>(); for (CodegenOperation op : operationList) { - for (CodegenParameter param : op.bodyParams) { - if (param.baseType != null && param.baseType.equalsIgnoreCase("Uint8List") && op.isMultipart) { + for (CodegenParameter param : op.allParams) { + if (((op.isMultipart && param.isFormParam) || param.isBodyParam) && (param.isBinary || param.isFile)) { param.baseType = "MultipartFile"; param.dataType = "MultipartFile"; + op.imports.add("MultipartFile"); } + } + + for (CodegenParameter param : op.bodyParams) { if (param.isContainer) { final Map serializer = new HashMap<>(); serializer.put("isArray", param.isArray); @@ -328,7 +333,12 @@ public class DartDioNextClientCodegen extends AbstractDartCodegen { } } - resultImports.addAll(rewriteImports(op.imports)); + if (op.allParams.stream().noneMatch(param -> param.dataType.equals("Uint8List"))) { + // Remove unused imports after processing + op.imports.remove("Uint8List"); + } + + resultImports.addAll(rewriteImports(op.imports, false)); if (op.getHasFormParams()) { resultImports.add("package:" + pubName + "/src/api_util.dart"); } @@ -349,11 +359,16 @@ public class DartDioNextClientCodegen extends AbstractDartCodegen { return objs; } - private Set rewriteImports(Set originalImports) { + private Set rewriteImports(Set originalImports, boolean isModel) { Set resultImports = Sets.newHashSet(); for (String modelImport : originalImports) { if (imports.containsKey(modelImport)) { - resultImports.add(imports.get(modelImport)); + String i = imports.get(modelImport); + if (Objects.equals(i, DIO_IMPORT) && !isModel) { + // Don't add imports to operations that are already imported + continue; + } + resultImports.add(i); } else { resultImports.add("package:" + pubName + "/src/model/" + underscore(modelImport) + ".dart"); } diff --git a/modules/openapi-generator/src/main/resources/dart/libraries/dio/serialization/built_value/serialize.mustache b/modules/openapi-generator/src/main/resources/dart/libraries/dio/serialization/built_value/serialize.mustache index 5794f34ad1a..03d38c93878 100644 --- a/modules/openapi-generator/src/main/resources/dart/libraries/dio/serialization/built_value/serialize.mustache +++ b/modules/openapi-generator/src/main/resources/dart/libraries/dio/serialization/built_value/serialize.mustache @@ -1,13 +1,13 @@ {{#hasFormParams}} _bodyData = {{#isMultipart}}FormData.fromMap({{/isMultipart}}{ {{#formParams}} - {{^required}}{{^isNullable}}if ({{{paramName}}} != null) {{/isNullable}}{{/required}}r'{{{baseName}}}': {{#isFile}}MultipartFile.fromBytes({{{paramName}}}, filename: r'{{{baseName}}}'){{/isFile}}{{^isFile}}encodeFormParameter(_serializers, {{{paramName}}}, const FullType({{^isContainer}}{{{baseType}}}{{/isContainer}}{{#isContainer}}Built{{#isMap}}Map{{/isMap}}{{#isArray}}{{#uniqueItems}}Set{{/uniqueItems}}{{^uniqueItems}}List{{/uniqueItems}}{{/isArray}}, [{{#isMap}}FullType(String), {{/isMap}}FullType({{{baseType}}})]{{/isContainer}})){{/isFile}}, + {{^required}}{{^isNullable}}if ({{{paramName}}} != null) {{/isNullable}}{{/required}}r'{{{baseName}}}': {{#isFile}}{{{paramName}}}{{/isFile}}{{^isFile}}encodeFormParameter(_serializers, {{{paramName}}}, const FullType({{^isContainer}}{{{baseType}}}{{/isContainer}}{{#isContainer}}Built{{#isMap}}Map{{/isMap}}{{#isArray}}{{#uniqueItems}}Set{{/uniqueItems}}{{^uniqueItems}}List{{/uniqueItems}}{{/isArray}}, [{{#isMap}}FullType(String), {{/isMap}}FullType({{{baseType}}})]{{/isContainer}})){{/isFile}}, {{/formParams}} }{{#isMultipart}}){{/isMultipart}}; {{/hasFormParams}} {{#bodyParam}} {{#isPrimitiveType}} - _bodyData = {{paramName}}; + _bodyData = {{paramName}}{{#isFile}}.finalize(){{/isFile}}; {{/isPrimitiveType}} {{^isPrimitiveType}} {{#isContainer}} diff --git a/samples/openapi3/client/petstore/dart-dio-next/petstore_client_lib_fake/doc/PetApi.md b/samples/openapi3/client/petstore/dart-dio-next/petstore_client_lib_fake/doc/PetApi.md index 841eed63291..bca230088d8 100644 --- a/samples/openapi3/client/petstore/dart-dio-next/petstore_client_lib_fake/doc/PetApi.md +++ b/samples/openapi3/client/petstore/dart-dio-next/petstore_client_lib_fake/doc/PetApi.md @@ -345,7 +345,7 @@ import 'package:openapi/api.dart'; var api_instance = new PetApi(); var petId = 789; // int | ID of pet to update var additionalMetadata = additionalMetadata_example; // String | Additional data to pass to server -var file = BINARY_DATA_HERE; // Uint8List | file to upload +var file = BINARY_DATA_HERE; // MultipartFile | file to upload try { var result = api_instance.uploadFile(petId, additionalMetadata, file); @@ -361,7 +361,7 @@ Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- **petId** | **int**| ID of pet to update | **additionalMetadata** | **String**| Additional data to pass to server | [optional] - **file** | **Uint8List**| file to upload | [optional] + **file** | **MultipartFile**| file to upload | [optional] ### Return type @@ -391,7 +391,7 @@ import 'package:openapi/api.dart'; var api_instance = new PetApi(); var petId = 789; // int | ID of pet to update -var requiredFile = BINARY_DATA_HERE; // Uint8List | file to upload +var requiredFile = BINARY_DATA_HERE; // MultipartFile | file to upload var additionalMetadata = additionalMetadata_example; // String | Additional data to pass to server try { @@ -407,7 +407,7 @@ try { Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- **petId** | **int**| ID of pet to update | - **requiredFile** | **Uint8List**| file to upload | + **requiredFile** | **MultipartFile**| file to upload | **additionalMetadata** | **String**| Additional data to pass to server | [optional] ### Return type diff --git a/samples/openapi3/client/petstore/dart-dio-next/petstore_client_lib_fake/lib/src/api/fake_api.dart b/samples/openapi3/client/petstore/dart-dio-next/petstore_client_lib_fake/lib/src/api/fake_api.dart index b5d03c9bda1..c9f67f4b736 100644 --- a/samples/openapi3/client/petstore/dart-dio-next/petstore_client_lib_fake/lib/src/api/fake_api.dart +++ b/samples/openapi3/client/petstore/dart-dio-next/petstore_client_lib_fake/lib/src/api/fake_api.dart @@ -851,7 +851,7 @@ class FakeApi { if (string != null) r'string': encodeFormParameter(_serializers, string, const FullType(String)), r'pattern_without_delimiter': encodeFormParameter(_serializers, patternWithoutDelimiter, const FullType(String)), r'byte': encodeFormParameter(_serializers, byte, const FullType(String)), - if (binary != null) r'binary': MultipartFile.fromBytes(binary, filename: r'binary'), + if (binary != null) r'binary': binary, if (date != null) r'date': encodeFormParameter(_serializers, date, const FullType(Date)), if (dateTime != null) r'dateTime': encodeFormParameter(_serializers, dateTime, const FullType(DateTime)), if (password != null) r'password': encodeFormParameter(_serializers, password, const FullType(String)), diff --git a/samples/openapi3/client/petstore/dart-dio-next/petstore_client_lib_fake/lib/src/api/pet_api.dart b/samples/openapi3/client/petstore/dart-dio-next/petstore_client_lib_fake/lib/src/api/pet_api.dart index e3f1473c5be..ede9b4e65ba 100644 --- a/samples/openapi3/client/petstore/dart-dio-next/petstore_client_lib_fake/lib/src/api/pet_api.dart +++ b/samples/openapi3/client/petstore/dart-dio-next/petstore_client_lib_fake/lib/src/api/pet_api.dart @@ -7,7 +7,6 @@ import 'dart:async'; import 'package:built_value/serializer.dart'; import 'package:dio/dio.dart'; -import 'dart:typed_data'; import 'package:built_collection/built_collection.dart'; import 'package:openapi/src/api_util.dart'; import 'package:openapi/src/model/api_response.dart'; @@ -493,7 +492,7 @@ class PetApi { Future> uploadFile({ required int petId, String? additionalMetadata, - Uint8List? file, + MultipartFile? file, CancelToken? cancelToken, Map? headers, Map? extra, @@ -528,7 +527,7 @@ class PetApi { try { _bodyData = FormData.fromMap({ if (additionalMetadata != null) r'additionalMetadata': encodeFormParameter(_serializers, additionalMetadata, const FullType(String)), - if (file != null) r'file': MultipartFile.fromBytes(file, filename: r'file'), + if (file != null) r'file': file, }); } catch(error, stackTrace) { @@ -588,7 +587,7 @@ class PetApi { /// Future> uploadFileWithRequiredFile({ required int petId, - required Uint8List requiredFile, + required MultipartFile requiredFile, String? additionalMetadata, CancelToken? cancelToken, Map? headers, @@ -624,7 +623,7 @@ class PetApi { try { _bodyData = FormData.fromMap({ if (additionalMetadata != null) r'additionalMetadata': encodeFormParameter(_serializers, additionalMetadata, const FullType(String)), - r'requiredFile': MultipartFile.fromBytes(requiredFile, filename: r'requiredFile'), + r'requiredFile': requiredFile, }); } catch(error, stackTrace) { diff --git a/samples/openapi3/client/petstore/dart-dio-next/petstore_client_lib_fake_tests/pubspec.yaml b/samples/openapi3/client/petstore/dart-dio-next/petstore_client_lib_fake_tests/pubspec.yaml index 6c33286cb5a..398c6a07470 100644 --- a/samples/openapi3/client/petstore/dart-dio-next/petstore_client_lib_fake_tests/pubspec.yaml +++ b/samples/openapi3/client/petstore/dart-dio-next/petstore_client_lib_fake_tests/pubspec.yaml @@ -8,11 +8,11 @@ environment: sdk: '>=2.10.0 <3.0.0' dev_dependencies: - built_collection: '5.0.0' - built_value: '8.0.4' - dio: '4.0.0' + built_collection: 5.0.0 + built_value: 8.0.6 + dio: 4.0.0 http_mock_adapter: 0.2.1 - mockito: '5.0.3' + mockito: 5.0.8 openapi: path: ../petstore_client_lib_fake - test: '1.16.8' + test: 1.17.4 diff --git a/samples/openapi3/client/petstore/dart-dio-next/petstore_client_lib_fake_tests/test/api/fake_api_test.dart b/samples/openapi3/client/petstore/dart-dio-next/petstore_client_lib_fake_tests/test/api/fake_api_test.dart index e1a40f57fe5..9330474d499 100644 --- a/samples/openapi3/client/petstore/dart-dio-next/petstore_client_lib_fake_tests/test/api/fake_api_test.dart +++ b/samples/openapi3/client/petstore/dart-dio-next/petstore_client_lib_fake_tests/test/api/fake_api_test.dart @@ -36,7 +36,7 @@ void main() { 'int64': '9223372036854775807', 'date': '2020-08-11', 'dateTime': '2020-08-11T12:30:55.123Z', - 'binary': "Instance of 'MultipartFile'", + 'binary': '[0, 1, 2, 3, 4, 5]', }, headers: { 'content-type': 'application/x-www-form-urlencoded', diff --git a/samples/openapi3/client/petstore/dart-dio-next/petstore_client_lib_fake_tests/test/api/pet_api_test.dart b/samples/openapi3/client/petstore/dart-dio-next/petstore_client_lib_fake_tests/test/api/pet_api_test.dart index 5aae7639fda..6b52fdce13e 100644 --- a/samples/openapi3/client/petstore/dart-dio-next/petstore_client_lib_fake_tests/test/api/pet_api_test.dart +++ b/samples/openapi3/client/petstore/dart-dio-next/petstore_client_lib_fake_tests/test/api/pet_api_test.dart @@ -1,9 +1,12 @@ import 'package:built_collection/built_collection.dart'; import 'package:dio/dio.dart'; import 'package:http_mock_adapter/http_mock_adapter.dart'; +import 'package:http_parser/http_parser.dart'; import 'package:openapi/openapi.dart'; import 'package:test/test.dart'; +import '../matcher/form_data_matcher.dart'; + void main() { const photo1 = 'https://localhost/photo1.jpg'; const photo2 = 'https://localhost/photo2.jpg'; @@ -221,5 +224,83 @@ void main() { expect(response.data[1].status, PetStatusEnum.available); }); }); + + group('uploadFile', () { + test('uploadFileWithRequiredFile', () async { + final file = MultipartFile.fromBytes( + [1, 2, 3, 4], + filename: 'test.png', + contentType: MediaType.parse('image/png'), + ); + + server.onRoute( + '/fake/5/uploadImageWithRequiredFile', + (request) => request.reply(200, { + 'code': 200, + 'type': 'success', + 'message': 'File uploaded', + }), + request: Request( + method: RequestMethods.post, + headers: { + Headers.contentTypeHeader: + Matchers.pattern('multipart/form-data'), + Headers.contentLengthHeader: Matchers.integer, + }, + data: FormDataMatcher( + expected: FormData.fromMap({ + r'requiredFile': file, + }), + ), + ), + ); + final response = await client.getPetApi().uploadFileWithRequiredFile( + petId: 5, + requiredFile: file, + ); + + expect(response.statusCode, 200); + expect(response.data.message, 'File uploaded'); + }); + + test('uploadFileWithRequiredFile & additionalMetadata', () async { + final file = MultipartFile.fromBytes( + [1, 2, 3, 4], + filename: 'test.png', + contentType: MediaType.parse('image/png'), + ); + + server.onRoute( + '/fake/3/uploadImageWithRequiredFile', + (request) => request.reply(200, { + 'code': 200, + 'type': 'success', + 'message': 'File uploaded', + }), + request: Request( + method: RequestMethods.post, + headers: { + Headers.contentTypeHeader: + Matchers.pattern('multipart/form-data'), + Headers.contentLengthHeader: Matchers.integer, + }, + data: FormDataMatcher( + expected: FormData.fromMap({ + 'additionalMetadata': 'foo', + r'requiredFile': file, + }), + ), + ), + ); + final response = await client.getPetApi().uploadFileWithRequiredFile( + petId: 3, + requiredFile: file, + additionalMetadata: 'foo', + ); + + expect(response.statusCode, 200); + expect(response.data.message, 'File uploaded'); + }); + }); }); } diff --git a/samples/openapi3/client/petstore/dart-dio-next/petstore_client_lib_fake_tests/test/matcher/form_data_matcher.dart b/samples/openapi3/client/petstore/dart-dio-next/petstore_client_lib_fake_tests/test/matcher/form_data_matcher.dart new file mode 100644 index 00000000000..8e0a7a6c032 --- /dev/null +++ b/samples/openapi3/client/petstore/dart-dio-next/petstore_client_lib_fake_tests/test/matcher/form_data_matcher.dart @@ -0,0 +1,26 @@ +import 'package:dio/dio.dart'; +import 'package:meta/meta.dart'; +import 'package:collection/collection.dart'; +import 'package:http_mock_adapter/src/matchers/matcher.dart'; + +class FormDataMatcher extends Matcher { + final FormData expected; + + const FormDataMatcher({@required this.expected}); + + @override + bool matches(dynamic actual) { + if (actual is! FormData) { + return false; + } + final data = actual as FormData; + return MapEquality().equals( + Map.fromEntries(expected.fields), + Map.fromEntries(data.fields), + ) && + MapEquality().equals( + Map.fromEntries(expected.files), + Map.fromEntries(data.files), + ); + } +}