diff --git a/chopper/example/definition.chopper.dart b/chopper/example/definition.chopper.dart index 0a26d4a8..f43a754d 100644 --- a/chopper/example/definition.chopper.dart +++ b/chopper/example/definition.chopper.dart @@ -6,7 +6,7 @@ part of 'definition.dart'; // ChopperGenerator // ************************************************************************** -// ignore_for_file: always_put_control_body_on_new_line, always_specify_types, prefer_const_declarations +// ignore_for_file: always_put_control_body_on_new_line, always_specify_types, prefer_const_declarations, unnecessary_brace_in_string_interps class _$MyService extends MyService { _$MyService([ChopperClient? client]) { if (client == null) return; @@ -18,7 +18,7 @@ class _$MyService extends MyService { @override Future> getResource(String id) { - final $url = '/resources/$id'; + final $url = '/resources/${id}'; final $request = Request('GET', $url, client.baseUrl); return client.send($request); } diff --git a/chopper/test/base_test.dart b/chopper/test/base_test.dart index b591168a..24487c36 100644 --- a/chopper/test/base_test.dart +++ b/chopper/test/base_test.dart @@ -591,6 +591,165 @@ void main() { }); }); + test('Query Map 3', () async { + final httpClient = MockClient((request) async { + expect( + request.url.toString(), + equals('$baseUrl/test/query_map?name=foo&number=1234'), + ); + expect(request.method, equals('GET')); + + return http.Response('get response', 200); + }); + + final chopper = buildClient(httpClient); + final service = chopper.getService(); + + final response = await service.getQueryMapTest3( + name: 'foo', + number: 1234, + ); + + expect(response.body, equals('get response')); + expect(response.statusCode, equals(200)); + + httpClient.close(); + }); + + test('Query Map 4 without QueryMap', () async { + final httpClient = MockClient((request) async { + expect( + request.url.toString(), + equals('$baseUrl/test/query_map?name=foo&number=1234'), + ); + expect(request.method, equals('GET')); + + return http.Response('get response', 200); + }); + + final chopper = buildClient(httpClient); + final service = chopper.getService(); + + final response = await service.getQueryMapTest4( + name: 'foo', + number: 1234, + ); + + expect(response.body, equals('get response')); + expect(response.statusCode, equals(200)); + + httpClient.close(); + }); + + test('Query Map 4 with QueryMap', () async { + final httpClient = MockClient((request) async { + expect( + request.url.toString(), + equals( + '$baseUrl/test/query_map?name=foo&number=1234&filter_1=filter_value_1', + ), + ); + expect(request.method, equals('GET')); + + return http.Response('get response', 200); + }); + + final chopper = buildClient(httpClient); + final service = chopper.getService(); + + final response = await service.getQueryMapTest4( + name: 'foo', + number: 1234, + filters: { + 'filter_1': 'filter_value_1', + }, + ); + + expect(response.body, equals('get response')); + expect(response.statusCode, equals(200)); + + httpClient.close(); + }); + + test( + 'Query Map 4 with QueryMap that overwrites a previous value from Query', + () async { + final httpClient = MockClient((request) async { + expect( + request.url.toString(), + equals('$baseUrl/test/query_map?name=bar&number=1234'), + ); + expect(request.method, equals('GET')); + + return http.Response('get response', 200); + }); + + final chopper = buildClient(httpClient); + final service = chopper.getService(); + + final response = await service.getQueryMapTest4( + name: 'foo', + number: 1234, + filters: { + 'name': 'bar', + }, + ); + + expect(response.body, equals('get response')); + expect(response.statusCode, equals(200)); + + httpClient.close(); + }, + ); + + test('Query Map 5 without QueryMap', () async { + final httpClient = MockClient((request) async { + expect( + request.url.toString(), + equals('$baseUrl/test/query_map'), + ); + expect(request.method, equals('GET')); + + return http.Response('get response', 200); + }); + + final chopper = buildClient(httpClient); + final service = chopper.getService(); + + final response = await service.getQueryMapTest5(); + + expect(response.body, equals('get response')); + expect(response.statusCode, equals(200)); + + httpClient.close(); + }); + + test('Query Map 5 with QueryMap', () async { + final httpClient = MockClient((request) async { + expect( + request.url.toString(), + equals('$baseUrl/test/query_map?filter_1=filter_value_1'), + ); + expect(request.method, equals('GET')); + + return http.Response('get response', 200); + }); + + final chopper = buildClient(httpClient); + final service = chopper.getService(); + + final response = await service.getQueryMapTest5( + filters: { + 'filter_1': 'filter_value_1', + }, + ); + + expect(response.body, equals('get response')); + expect(response.statusCode, equals(200)); + + httpClient.close(); + }); + test('onRequest Stream', () async { final client = MockClient((http.Request req) async { return http.Response('ok', 200); diff --git a/chopper/test/test_service.chopper.dart b/chopper/test/test_service.chopper.dart index 2d03d152..f1fd6769 100644 --- a/chopper/test/test_service.chopper.dart +++ b/chopper/test/test_service.chopper.dart @@ -6,7 +6,7 @@ part of 'test_service.dart'; // ChopperGenerator // ************************************************************************** -// ignore_for_file: always_put_control_body_on_new_line, always_specify_types, prefer_const_declarations +// ignore_for_file: always_put_control_body_on_new_line, always_specify_types, prefer_const_declarations, unnecessary_brace_in_string_interps class _$HttpTestService extends HttpTestService { _$HttpTestService([ChopperClient? client]) { if (client == null) return; @@ -18,7 +18,7 @@ class _$HttpTestService extends HttpTestService { @override Future> getTest(String id, {required String dynamicHeader}) { - final $url = '/test/get/$id'; + final $url = '/test/get/${id}'; final $headers = { 'test': dynamicHeader, }; @@ -93,6 +93,36 @@ class _$HttpTestService extends HttpTestService { return client.send($request); } + @override + Future> getQueryMapTest3( + {String name = '', + int? number, + Map filters = const {}}) { + final $url = '/test/query_map'; + final $params = {'name': name, 'number': number}; + $params.addAll(filters); + final $request = Request('GET', $url, client.baseUrl, parameters: $params); + return client.send($request); + } + + @override + Future> getQueryMapTest4( + {String name = '', int? number, Map? filters}) { + final $url = '/test/query_map'; + final $params = {'name': name, 'number': number}; + $params.addAll(filters ?? {}); + final $request = Request('GET', $url, client.baseUrl, parameters: $params); + return client.send($request); + } + + @override + Future> getQueryMapTest5({Map? filters}) { + final $url = '/test/query_map'; + final $params = filters ?? {}; + final $request = Request('GET', $url, client.baseUrl, parameters: $params); + return client.send($request); + } + @override Future> getBody(dynamic body) { final $url = '/test/get_body'; @@ -119,7 +149,7 @@ class _$HttpTestService extends HttpTestService { @override Future> putTest(String test, String data) { - final $url = '/test/put/$test'; + final $url = '/test/put/${test}'; final $body = data; final $request = Request('PUT', $url, client.baseUrl, body: $body); return client.send($request); @@ -127,7 +157,7 @@ class _$HttpTestService extends HttpTestService { @override Future> deleteTest(String id) { - final $url = '/test/delete/$id'; + final $url = '/test/delete/${id}'; final $headers = { 'foo': 'bar', }; @@ -138,7 +168,7 @@ class _$HttpTestService extends HttpTestService { @override Future> patchTest(String id, String data) { - final $url = '/test/patch/$id'; + final $url = '/test/patch/${id}'; final $body = data; final $request = Request('PATCH', $url, client.baseUrl, body: $body); return client.send($request); diff --git a/chopper/test/test_service.dart b/chopper/test/test_service.dart index 1c68866a..fa137441 100644 --- a/chopper/test/test_service.dart +++ b/chopper/test/test_service.dart @@ -48,6 +48,25 @@ abstract class HttpTestService extends ChopperService { @Query('test') bool? test, }); + @Get(path: 'query_map') + Future getQueryMapTest3({ + @Query('name') String name = '', + @Query('number') int? number, + @QueryMap() Map filters = const {}, + }); + + @Get(path: 'query_map') + Future getQueryMapTest4({ + @Query('name') String name = '', + @Query('number') int? number, + @QueryMap() Map? filters, + }); + + @Get(path: 'query_map') + Future getQueryMapTest5({ + @QueryMap() Map? filters, + }); + @Get(path: 'get_body') Future getBody(@Body() dynamic body); diff --git a/chopper_generator/lib/src/generator.dart b/chopper_generator/lib/src/generator.dart index a3ae9880..ab95ee80 100644 --- a/chopper_generator/lib/src/generator.dart +++ b/chopper_generator/lib/src/generator.dart @@ -10,6 +10,7 @@ import 'package:built_collection/built_collection.dart'; import 'package:dart_style/dart_style.dart'; import 'package:source_gen/source_gen.dart'; + // TODO(lejard_h) Code builder not null safe yet // ignore: import_of_legacy_library_into_null_safe import 'package:code_builder/code_builder.dart'; @@ -171,15 +172,34 @@ class ChopperGenerator extends GeneratorForAnnotation { blocks.add(_generateMap(queries).assignFinal(_parametersVar).statement); } + // Build an iterable of all the parameters that are nullable + final optionalNullableParameters = [ + ...m.parameters.where((p) => p.isOptionalPositional), + ...m.parameters.where((p) => p.isNamed), + ].where((el) => el.type.isNullable).map((el) => el.name); + final hasQueryMap = queryMap.isNotEmpty; if (hasQueryMap) { if (queries.isNotEmpty) { blocks.add(refer('$_parametersVar.addAll').call( - [refer(queryMap.keys.first)], + [ + // Check if the parameter is nullable + optionalNullableParameters.contains(queryMap.keys.first) + ? refer(queryMap.keys.first).ifNullThen(refer('{}')) + : refer(queryMap.keys.first), + ], ).statement); } else { blocks.add( - refer(queryMap.keys.first).assignFinal(_parametersVar).statement, + // Check if the parameter is nullable + optionalNullableParameters.contains(queryMap.keys.first) + ? refer(queryMap.keys.first) + .ifNullThen(refer('{}')) + .assignFinal(_parametersVar) + .statement + : refer(queryMap.keys.first) + .assignFinal(_parametersVar) + .statement, ); } }