From b0fd18f7e1fb46968dc4a47f3dd61b69aaaaa92e Mon Sep 17 00:00:00 2001 From: Job Guldemeester Date: Mon, 31 Oct 2022 07:06:22 +0100 Subject: [PATCH] [Feature] Replace the String based path with Uri (#383) --- chopper/example/definition.chopper.dart | 12 +- chopper/example/main.dart | 2 +- chopper/lib/src/base.dart | 50 +++--- chopper/lib/src/request.dart | 92 +++++----- chopper/test/base_test.dart | 168 +++++++++++++++--- chopper/test/client_test.dart | 38 ++-- chopper/test/converter_test.dart | 9 +- chopper/test/interceptors_test.dart | 14 +- chopper/test/multipart_test.dart | 15 +- chopper/test/request_test.dart | 145 ++++++++++++--- chopper/test/test_service.chopper.dart | 72 ++++---- chopper_built_value/test/converter_test.dart | 6 +- chopper_generator/lib/src/generator.dart | 19 +- example/bin/main_built_value.dart | 2 +- example/bin/main_json_serializable.dart | 2 +- ...son_serializable_squadron_worker_pool.dart | 2 +- example/lib/built_value_resource.chopper.dart | 8 +- example/lib/json_serializable.chopper.dart | 10 +- faq.md | 8 +- 19 files changed, 467 insertions(+), 207 deletions(-) diff --git a/chopper/example/definition.chopper.dart b/chopper/example/definition.chopper.dart index 884b3470..c7c86ef7 100644 --- a/chopper/example/definition.chopper.dart +++ b/chopper/example/definition.chopper.dart @@ -18,7 +18,7 @@ class _$MyService extends MyService { @override Future> getResource(String id) { - final String $url = '/resources/${id}'; + final Uri $url = Uri.parse('/resources/${id}'); final Request $request = Request( 'GET', $url, @@ -29,7 +29,7 @@ class _$MyService extends MyService { @override Future>> getMapResource(String id) { - final String $url = '/resources/'; + final Uri $url = Uri.parse('/resources/'); final Map $params = {'id': id}; final Map $headers = { 'foo': 'bar', @@ -46,7 +46,7 @@ class _$MyService extends MyService { @override Future>>> getListResources() { - final String $url = '/resources/resources'; + final Uri $url = Uri.parse('/resources/resources'); final Request $request = Request( 'GET', $url, @@ -61,7 +61,7 @@ class _$MyService extends MyService { String toto, String b, ) { - final String $url = '/resources/'; + final Uri $url = Uri.parse('/resources/'); final $body = { 'a': toto, 'b': b, @@ -81,7 +81,7 @@ class _$MyService extends MyService { Map b, String c, ) { - final String $url = '/resources/multi'; + final Uri $url = Uri.parse('/resources/multi'); final List $parts = [ PartValue>( '1', @@ -108,7 +108,7 @@ class _$MyService extends MyService { @override Future> postFile(List bytes) { - final String $url = '/resources/file'; + final Uri $url = Uri.parse('/resources/file'); final List $parts = [ PartValue>( 'file', diff --git a/chopper/example/main.dart b/chopper/example/main.dart index 16f66abc..b7a2cad4 100644 --- a/chopper/example/main.dart +++ b/chopper/example/main.dart @@ -4,7 +4,7 @@ import 'definition.dart'; Future main() async { final chopper = ChopperClient( - baseUrl: 'http://localhost:8000', + baseUrl: Uri.parse('http://localhost:8000'), services: [ // the generated service MyService.create(ChopperClient()), diff --git a/chopper/lib/src/base.dart b/chopper/lib/src/base.dart index 4e594584..624607b3 100644 --- a/chopper/lib/src/base.dart +++ b/chopper/lib/src/base.dart @@ -29,7 +29,7 @@ final List allowedInterceptorsType = [ class ChopperClient { /// Base URL of each request of the registered services. /// E.g., the hostname of your service. - final String baseUrl; + final Uri baseUrl; /// The [http.Client] used to make network calls. final http.Client httpClient; @@ -60,6 +60,7 @@ class ChopperClient { /// The base URL of each request of the registered services can be defined /// with the [baseUrl] parameter. /// E.g., the hostname of your service. + /// If not provided, a empty default [Uri] will be used. /// /// A custom HTTP client can be passed as the [client] parameter to be used /// with the created [ChopperClient]. @@ -70,7 +71,7 @@ class ChopperClient { /// /// ```dart /// final chopper = ChopperClient( - /// baseUrl: 'localhost:8000', + /// baseUrl: Uri.parse('localhost:8000'), /// services: [ /// // Add a generated service /// TodosListService.create() @@ -111,14 +112,19 @@ class ChopperClient { /// ); /// ``` ChopperClient({ - this.baseUrl = '', + Uri? baseUrl, http.Client? client, Iterable interceptors = const [], this.authenticator, this.converter, this.errorConverter, Iterable services = const [], - }) : httpClient = client ?? http.Client(), + }) : assert( + baseUrl == null || !baseUrl.hasQuery, + 'baseUrl should not contain query parameters.' + 'Use a request interceptor to add default query parameters'), + baseUrl = baseUrl ?? Uri(), + httpClient = client ?? http.Client(), _clientIsInternal = client == null { if (!interceptors.every(_isAnInterceptor)) { throw ArgumentError( @@ -152,7 +158,7 @@ class ChopperClient { /// /// ```dart /// final chopper = ChopperClient( - /// baseUrl: 'localhost:8000', + /// baseUrl: Uri.parse('localhost:8000'), /// services: [ /// // Add a generated service /// TodosListService.create() @@ -341,10 +347,10 @@ class ChopperClient { /// Makes a HTTP GET request using the [send] function. Future> get( - String url, { + Uri url, { Map headers = const {}, + Uri? baseUrl, Map parameters = const {}, - String? baseUrl, dynamic body, }) => send( @@ -360,13 +366,13 @@ class ChopperClient { /// Makes a HTTP POST request using the [send] function Future> post( - String url, { + Uri url, { dynamic body, List parts = const [], Map headers = const {}, Map parameters = const {}, bool multipart = false, - String? baseUrl, + Uri? baseUrl, }) => send( Request( @@ -376,20 +382,20 @@ class ChopperClient { body: body, parts: parts, headers: headers, - multipart: multipart, parameters: parameters, + multipart: multipart, ), ); /// Makes a HTTP PUT request using the [send] function. Future> put( - String url, { + Uri url, { dynamic body, List parts = const [], Map headers = const {}, Map parameters = const {}, bool multipart = false, - String? baseUrl, + Uri? baseUrl, }) => send( Request( @@ -399,20 +405,20 @@ class ChopperClient { body: body, parts: parts, headers: headers, - multipart: multipart, parameters: parameters, + multipart: multipart, ), ); /// Makes a HTTP PATCH request using the [send] function. Future> patch( - String url, { + Uri url, { dynamic body, List parts = const [], Map headers = const {}, Map parameters = const {}, bool multipart = false, - String? baseUrl, + Uri? baseUrl, }) => send( Request( @@ -422,17 +428,17 @@ class ChopperClient { body: body, parts: parts, headers: headers, - multipart: multipart, parameters: parameters, + multipart: multipart, ), ); /// Makes a HTTP DELETE request using the [send] function. Future> delete( - String url, { + Uri url, { Map headers = const {}, Map parameters = const {}, - String? baseUrl, + Uri? baseUrl, }) => send( Request( @@ -446,10 +452,10 @@ class ChopperClient { /// Makes a HTTP HEAD request using the [send] function. Future> head( - String url, { + Uri url, { Map headers = const {}, Map parameters = const {}, - String? baseUrl, + Uri? baseUrl, }) => send( Request( @@ -463,10 +469,10 @@ class ChopperClient { /// Makes a HTTP OPTIONS request using the [send] function. Future> options( - String url, { + Uri url, { Map headers = const {}, Map parameters = const {}, - String? baseUrl, + Uri? baseUrl, }) => send( Request( diff --git a/chopper/lib/src/request.dart b/chopper/lib/src/request.dart index f4189cc9..e418f2c2 100644 --- a/chopper/lib/src/request.dart +++ b/chopper/lib/src/request.dart @@ -7,8 +7,8 @@ import 'package:meta/meta.dart'; /// This class represents an HTTP request that can be made with Chopper. class Request extends http.BaseRequest { - final String path; - final String origin; + final Uri uri; + final Uri baseUri; final dynamic body; final Map parameters; final bool multipart; @@ -18,34 +18,8 @@ class Request extends http.BaseRequest { Request( String method, - this.path, - this.origin, { - this.body, - this.parameters = const {}, - Map headers = const {}, - this.multipart = false, - this.parts = const [], - this.useBrackets = false, - this.includeNullQueryVars = false, - }) : super( - method, - buildUri( - origin, - path, - parameters, - useBrackets: useBrackets, - includeNullQueryVars: includeNullQueryVars, - ), - ) { - this.headers.addAll(headers); - } - - /// Build the Chopper [Request] using a [Uri] instead of a [path] and [origin]. - /// Both the query parameters in the [Uri] and those provided explicitly in - /// the [parameters] are merged together. - Request.uri( - String method, - Uri url, { + this.uri, + this.baseUri, { this.body, Map? parameters, Map headers = const {}, @@ -53,15 +27,18 @@ class Request extends http.BaseRequest { this.parts = const [], this.useBrackets = false, this.includeNullQueryVars = false, - }) : origin = url.origin, - path = url.path, - parameters = {...url.queryParametersAll, ...?parameters}, + }) : assert( + !baseUri.hasQuery, + 'baseUri should not contain query parameters.' + 'Use a request interceptor to add default query parameters'), + // Merge uri.queryParametersAll in the final parameters object so the request object reflects all configured queryParameters + parameters = {...uri.queryParametersAll, ...?parameters}, super( method, buildUri( - url.origin, - url.path, - {...url.queryParametersAll, ...?parameters}, + baseUri, + uri, + {...uri.queryParametersAll, ...?parameters}, useBrackets: useBrackets, includeNullQueryVars: includeNullQueryVars, ), @@ -72,8 +49,8 @@ class Request extends http.BaseRequest { /// Makes a copy of this [Request], replacing original values with the given ones. Request copyWith({ String? method, - String? path, - String? origin, + Uri? uri, + Uri? baseUri, dynamic body, Map? parameters, Map? headers, @@ -84,8 +61,8 @@ class Request extends http.BaseRequest { }) => Request( method ?? this.method, - path ?? this.path, - origin ?? this.origin, + uri ?? this.uri, + baseUri ?? this.baseUri, body: body ?? this.body, parameters: parameters ?? this.parameters, headers: headers ?? this.headers, @@ -100,27 +77,44 @@ class Request extends http.BaseRequest { /// If [url] starts with 'http://' or 'https://', baseUrl is ignored. @visibleForTesting static Uri buildUri( - String baseUrl, - String url, + Uri baseUrl, + Uri url, Map parameters, { bool useBrackets = false, bool includeNullQueryVars = false, }) { // If the request's url is already a fully qualified URL, we can use it // as-is and ignore the baseUrl. - final Uri uri = url.startsWith('http://') || url.startsWith('https://') - ? Uri.parse(url) - : Uri.parse('${baseUrl.strip('/')}/${url.leftStrip('/')}'); + final Uri uri = url.isScheme('HTTP') || url.isScheme('HTTPS') + ? url + : _mergeUri(baseUrl, url); + + // Check if parameter also has all the queryParameters from the url (not the merged uri) + final bool parametersContainsUriQuery = parameters.keys + .every((element) => url.queryParametersAll.keys.contains(element)); + final Map allParameters = parametersContainsUriQuery + ? parameters + : {...url.queryParametersAll, ...parameters}; final String query = mapToQuery( - parameters, + allParameters, useBrackets: useBrackets, includeNullQueryVars: includeNullQueryVars, ); - return query.isNotEmpty - ? uri.replace(query: uri.hasQuery ? '${uri.query}&$query' : query) - : uri; + return query.isNotEmpty ? uri.replace(query: query) : uri; + } + + /// Merges Uri into another Uri preserving queries and paths + static Uri _mergeUri(Uri baseUri, Uri addToUri) { + final path = baseUri.hasEmptyPath + ? addToUri.path + : '${baseUri.path.rightStrip('/')}/${addToUri.path.leftStrip('/')}'; + + return baseUri.replace( + path: path, + query: addToUri.hasQuery ? addToUri.query : null, + ); } /// Converts this Chopper Request into a [http.BaseRequest]. diff --git a/chopper/test/base_test.dart b/chopper/test/base_test.dart index 42b2a39c..d6486c2e 100644 --- a/chopper/test/base_test.dart +++ b/chopper/test/base_test.dart @@ -10,7 +10,7 @@ import 'package:test/test.dart'; import 'test_service.dart'; -const baseUrl = 'http://localhost:8000'; +final baseUrl = Uri.parse('http://localhost:8000'); void main() { ChopperClient buildClient([ @@ -404,7 +404,7 @@ void main() { test('applyHeader', () { final req1 = applyHeader( - Request('GET', '/', baseUrl), + Request('GET', Uri.parse('/'), baseUrl), 'foo', 'bar', ); @@ -412,7 +412,7 @@ void main() { expect(req1.headers, equals({'foo': 'bar'})); final req2 = applyHeader( - Request('GET', '/', baseUrl, headers: {'foo': 'bar'}), + Request('GET', Uri.parse('/'), baseUrl, headers: {'foo': 'bar'}), 'bar', 'foo', ); @@ -420,7 +420,7 @@ void main() { expect(req2.headers, equals({'foo': 'bar', 'bar': 'foo'})); final req3 = applyHeader( - Request('GET', '/', baseUrl, headers: {'foo': 'bar'}), + Request('GET', Uri.parse('/'), baseUrl, headers: {'foo': 'bar'}), 'foo', 'foo', ); @@ -429,19 +429,20 @@ void main() { }); test('applyHeaders', () { - final req1 = applyHeaders(Request('GET', '/', baseUrl), {'foo': 'bar'}); + final req1 = + applyHeaders(Request('GET', Uri.parse('/'), baseUrl), {'foo': 'bar'}); expect(req1.headers, equals({'foo': 'bar'})); final req2 = applyHeaders( - Request('GET', '/', baseUrl, headers: {'foo': 'bar'}), + Request('GET', Uri.parse('/'), baseUrl, headers: {'foo': 'bar'}), {'bar': 'foo'}, ); expect(req2.headers, equals({'foo': 'bar', 'bar': 'foo'})); final req3 = applyHeaders( - Request('GET', '/', baseUrl, headers: {'foo': 'bar'}), + Request('GET', Uri.parse('/'), baseUrl, headers: {'foo': 'bar'}), {'foo': 'foo'}, ); @@ -471,49 +472,56 @@ void main() { test('url concatenation', () async { expect( - Request.buildUri('foo', 'bar', {}).toString(), + Request.buildUri(Uri.parse('foo'), Uri.parse('bar'), {}).toString(), equals('foo/bar'), ); expect( - Request.buildUri('foo/', 'bar', {}).toString(), + Request.buildUri(Uri.parse('foo/'), Uri.parse('bar'), {}).toString(), equals('foo/bar'), ); expect( - Request.buildUri('foo', '/bar', {}).toString(), + Request.buildUri(Uri.parse('foo'), Uri.parse('/bar'), {}).toString(), equals('foo/bar'), ); expect( - Request.buildUri('foo/', '/bar', {}).toString(), + Request.buildUri(Uri.parse('foo/'), Uri.parse('/bar'), {}).toString(), equals('foo/bar'), ); expect( - Request.buildUri('http://foo', '/bar', {}).toString(), + Request.buildUri(Uri.parse('http://foo'), Uri.parse('/bar'), {}) + .toString(), equals('http://foo/bar'), ); expect( - Request.buildUri('https://foo', '/bar', {}).toString(), + Request.buildUri(Uri.parse('https://foo'), Uri.parse('/bar'), {}) + .toString(), equals('https://foo/bar'), ); expect( - Request.buildUri('https://foo/', '/bar', {}).toString(), + Request.buildUri(Uri.parse('https://foo/'), Uri.parse('/bar'), {}) + .toString(), equals('https://foo/bar'), ); expect( - Request.buildUri('https://foo/', '/bar', {'abc': 'xyz'}).toString(), + Request.buildUri( + Uri.parse('https://foo/'), + Uri.parse('/bar'), + {'abc': 'xyz'}, + ).toString(), equals('https://foo/bar?abc=xyz'), ); expect( Request.buildUri( - 'https://foo/', - '/bar?first=123&second=456', + Uri.parse('https://foo/'), + Uri.parse('/bar?first=123&second=456'), { 'third': '789', 'fourth': '012', @@ -521,12 +529,81 @@ void main() { ).toString(), equals('https://foo/bar?first=123&second=456&third=789&fourth=012'), ); + + expect( + Request.buildUri( + Uri.parse('https://foo?first=123&second=456'), + Uri.parse('/bar'), + { + 'third': '789', + 'fourth': '012', + }, + ).toString(), + equals('https://foo/bar?third=789&fourth=012'), + ); + + expect( + Request.buildUri( + Uri.parse('https://foo?first=123&second=456'), + Uri.parse('/bar?third=789&fourth=012'), + { + 'fifth': '345', + 'sixth': '678', + }, + ).toString(), + equals( + 'https://foo/bar?third=789&fourth=012&fifth=345&sixth=678', + ), + ); + + expect( + Request.buildUri( + Uri.parse('https://foo.bar/foobar'), + Uri.parse('whatbar'), + {}, + ).toString(), + equals('https://foo.bar/foobar/whatbar'), + ); + + expect( + Request.buildUri( + Uri.parse('https://foo/bar?first=123&second=456'), + Uri.parse('https://bar/foo?fourth=789&fifth=012'), + {}, + ).toString(), + equals('https://bar/foo?fourth=789&fifth=012'), + ); + + expect( + Request('GET', Uri(path: '/bar'), Uri.parse('foo')).url.toString(), + equals('foo/bar'), + ); + + expect( + Request('GET', Uri(host: 'bar'), Uri.parse('foo')).url.toString(), + equals('foo/'), + ); + + expect( + Request('GET', Uri.https('bar'), Uri.parse('foo')).url.toString(), + equals('https://bar'), + ); + + expect( + Request( + 'GET', + Uri(scheme: 'https', host: 'bar', port: 666), + Uri.parse('foo'), + ).url.toString(), + equals('https://bar:666'), + ); }); test('BodyBytes', () { - final request = Request.uri( + final request = Request( HttpMethod.Post, Uri.parse('https://foo/'), + Uri.parse(''), body: [1, 2, 3], ).toHttpRequest(); @@ -534,9 +611,10 @@ void main() { }); test('BodyFields', () { - final request = Request.uri( + final request = Request( HttpMethod.Post, Uri.parse('https://foo/'), + Uri.parse(''), body: {'foo': 'bar'}, ).toHttpRequest(); @@ -545,9 +623,10 @@ void main() { test('Wrong body', () { try { - Request.uri( + Request( HttpMethod.Post, Uri.parse('https://foo/'), + Uri.parse(''), body: {'foo': 42}, ).toHttpRequest(); } on ArgumentError catch (e) { @@ -1179,4 +1258,53 @@ void main() { httpClient.close(); }); + + test('client baseUrl cannot contain query parameters', () { + expect( + () => ChopperClient( + baseUrl: Uri.http( + 'foo', + 'bar', + { + 'first': '123', + 'second': '456', + }, + ), + ), + throwsA( + TypeMatcher(), + ), + ); + + expect( + () => ChopperClient( + baseUrl: Uri.parse('foo/bar?first=123'), + ), + throwsA( + TypeMatcher(), + ), + ); + + expect( + () => ChopperClient( + baseUrl: Uri( + queryParameters: { + 'first': '123', + 'second': '456', + }, + ), + ), + throwsA( + TypeMatcher(), + ), + ); + expect( + () => ChopperClient( + baseUrl: Uri(query: 'first=123&second=456'), + ), + throwsA( + TypeMatcher(), + ), + ); + }); } diff --git a/chopper/test/client_test.dart b/chopper/test/client_test.dart index babb172d..88b0d2a1 100644 --- a/chopper/test/client_test.dart +++ b/chopper/test/client_test.dart @@ -5,7 +5,7 @@ import 'package:http/http.dart' as http; import 'package:http/testing.dart'; import 'package:test/test.dart'; -const baseUrl = 'http://localhost:8000'; +final baseUrl = Uri.parse('http://localhost:8000'); void main() { ChopperClient buildClient([http.Client? httpClient]) => ChopperClient( @@ -33,9 +33,11 @@ void main() { final chopper = buildClient(httpClient); final response = await chopper.get( - '/test/get', + Uri( + path: '/test/get', + queryParameters: {'key': 'val'}, + ), headers: {'int': '42'}, - parameters: {'key': 'val'}, ); expect(response.body, equals('get response')); @@ -59,10 +61,12 @@ void main() { final chopper = buildClient(httpClient); final response = await chopper.post( - '/test/post', + Uri( + path: '/test/post', + queryParameters: {'key': 'val'}, + ), headers: {'int': '42'}, body: {'content': 'body'}, - parameters: {'key': 'val'}, ); expect(response.body, equals('post response')); @@ -87,10 +91,12 @@ void main() { final chopper = buildClient(httpClient); final response = await chopper.put( - '/test/put', + Uri( + path: '/test/put', + queryParameters: {'key': 'val'}, + ), headers: {'int': '42'}, body: {'content': 'body'}, - parameters: {'key': 'val'}, ); expect(response.body, equals('put response')); @@ -115,10 +121,12 @@ void main() { final chopper = buildClient(httpClient); final response = await chopper.patch( - '/test/patch', + Uri( + path: '/test/patch', + queryParameters: {'key': 'val'}, + ), headers: {'int': '42'}, body: {'content': 'body'}, - parameters: {'key': 'val'}, ); expect(response.body, equals('patch response')); @@ -142,9 +150,11 @@ void main() { final chopper = buildClient(httpClient); final response = await chopper.delete( - '/test/delete', + Uri( + path: '/test/delete', + queryParameters: {'key': 'val'}, + ), headers: {'int': '42'}, - parameters: {'key': 'val'}, ); expect(response.body, equals('delete response')); @@ -167,9 +177,11 @@ void main() { final chopper = buildClient(httpClient); final response = await chopper.options( - '/test/get', + Uri( + path: '/test/get', + queryParameters: {'key': 'val'}, + ), headers: {'int': '42'}, - parameters: {'key': 'val'}, ); expect(response.body, equals('get response')); diff --git a/chopper/test/converter_test.dart b/chopper/test/converter_test.dart index 2fae6736..6b5d868c 100644 --- a/chopper/test/converter_test.dart +++ b/chopper/test/converter_test.dart @@ -7,7 +7,7 @@ import 'package:test/test.dart'; import 'test_service.dart'; -const baseUrl = 'http://localhost:8000'; +final baseUrl = Uri.parse('http://localhost:8000'); void main() { group('Converter', () { @@ -34,7 +34,12 @@ void main() { final converter = TestConverter(); final encoded = converter.convertRequest( - Request('GET', '/', baseUrl, body: _Converted('foo')), + Request( + 'GET', + Uri.parse('/'), + baseUrl, + body: _Converted('foo'), + ), ); expect(encoded.body is String, isTrue); diff --git a/chopper/test/interceptors_test.dart b/chopper/test/interceptors_test.dart index 16257dca..bffcd040 100644 --- a/chopper/test/interceptors_test.dart +++ b/chopper/test/interceptors_test.dart @@ -47,8 +47,9 @@ void main() { test('RequestInterceptorFunc', () async { final chopper = ChopperClient( interceptors: [ - (Request request) => - request.copyWith(path: '${request.url}/intercept'), + (Request request) => request.copyWith( + uri: request.uri.replace(path: '${request.uri.path}/intercept'), + ), ], services: [ HttpTestService.create(), @@ -184,8 +185,8 @@ void main() { final fakeRequest = Request( 'POST', - '/', - 'base', + Uri.parse('/'), + Uri.parse('base'), body: 'test', headers: {'foo': 'bar'}, ); @@ -270,8 +271,9 @@ class ResponseIntercept implements ResponseInterceptor { class RequestIntercept implements RequestInterceptor { @override - FutureOr onRequest(Request request) => - request.copyWith(path: '${request.url}/intercept'); + FutureOr onRequest(Request request) => request.copyWith( + uri: request.uri.replace(path: '${request.uri}/intercept'), + ); } class _Intercepted { diff --git a/chopper/test/multipart_test.dart b/chopper/test/multipart_test.dart index 1e461d2f..9bffea50 100644 --- a/chopper/test/multipart_test.dart +++ b/chopper/test/multipart_test.dart @@ -203,9 +203,10 @@ void main() { }); test('PartValue', () async { - final req = await Request.uri( + final req = await Request( HttpMethod.Post, Uri.parse('https://foo/'), + Uri.parse(''), parts: [ PartValue('foo', 'bar'), PartValue('int', 42), @@ -219,9 +220,10 @@ void main() { test( 'PartFile', () async { - final req = await Request.uri( + final req = await Request( HttpMethod.Post, Uri.parse('https://foo/'), + Uri.parse(''), parts: [ PartValueFile('foo', 'test/multipart_test.dart'), PartValueFile>('int', [1, 2]), @@ -257,9 +259,10 @@ void main() { }); test('Multipart request non nullable', () async { - final req = await Request.uri( + final req = await Request( HttpMethod.Post, Uri.parse('https://foo/'), + Uri.parse(''), parts: [ PartValue('int', 42), PartValueFile>('list int', [1, 2]), @@ -276,9 +279,10 @@ void main() { }); test('PartValue with MultipartFile directly', () async { - final req = await Request.uri( + final req = await Request( HttpMethod.Post, Uri.parse('https://foo/'), + Uri.parse(''), parts: [ PartValue( '', @@ -314,9 +318,10 @@ void main() { test('Throw exception', () async { expect( - () async => await Request.uri( + () async => await Request( HttpMethod.Post, Uri.parse('https://foo/'), + Uri.parse(''), parts: [ PartValueFile('', 123), ], diff --git a/chopper/test/request_test.dart b/chopper/test/request_test.dart index 6f886afd..45d9e2e0 100644 --- a/chopper/test/request_test.dart +++ b/chopper/test/request_test.dart @@ -1,42 +1,46 @@ // ignore_for_file: long-method import 'package:chopper/chopper.dart'; -import 'package:test/test.dart'; -import 'package:http/http.dart' as http; import 'package:collection/collection.dart'; +import 'package:http/http.dart' as http; +import 'package:test/test.dart'; void main() { group('Request', () { test('constructor produces a BaseRequest', () { expect( - Request('GET', '/bar', 'https://foo/'), + Request('GET', Uri.parse('/bar'), Uri.parse('https://foo/')), isA(), ); }); test('method gets preserved in BaseRequest', () { expect( - Request('GET', '/bar', 'https://foo/').method, + Request('GET', Uri.parse('/bar'), Uri.parse('https://foo/')).method, equals('GET'), ); }); test('url is correctly parsed and set in BaseRequest', () { expect( - Request('GET', '/bar', 'https://foo/').url, + Request('GET', Uri.parse('/bar'), Uri.parse('https://foo/')).url, equals(Uri.parse('https://foo/bar')), ); expect( - Request('GET', '/bar?lorem=ipsum&dolor=123', 'https://foo/').url, + Request( + 'GET', + Uri.parse('/bar?lorem=ipsum&dolor=123'), + Uri.parse('https://foo/'), + ).url, equals(Uri.parse('https://foo/bar?lorem=ipsum&dolor=123')), ); expect( Request( 'GET', - '/bar', - 'https://foo/', + Uri.parse('/bar'), + Uri.parse('https://foo/'), parameters: { 'lorem': 'ipsum', 'dolor': 123, @@ -48,8 +52,8 @@ void main() { expect( Request( 'GET', - '/bar?first=sit&second=amet&first_list=a&first_list=b', - 'https://foo/', + Uri.parse('/bar?first=sit&second=amet&first_list=a&first_list=b'), + Uri.parse('https://foo/'), parameters: { 'lorem': 'ipsum', 'dolor': 123, @@ -70,8 +74,8 @@ void main() { final Request request = Request( 'GET', - '/bar', - 'https://foo/', + Uri.parse('/bar'), + Uri.parse('https://foo/'), headers: headers, ); @@ -83,43 +87,48 @@ void main() { test('copyWith creates a BaseRequest', () { expect( - Request('GET', '/bar', 'https://foo/').copyWith(method: HttpMethod.Put), + Request('GET', Uri.parse('/bar'), Uri.parse('https://foo/')) + .copyWith(method: HttpMethod.Put), isA(), ); }); }); - group('Request.uri', () { + group('Request', () { test('constructor produces a BaseRequest', () { expect( - Request.uri('GET', Uri.parse('https://foo/bar')), + Request('GET', Uri.parse('https://foo/bar'), Uri.parse('')), isA(), ); }); test('method gets preserved in BaseRequest', () { expect( - Request.uri('GET', Uri.parse('https://foo/bar')).method, + Request('GET', Uri.parse('https://foo/bar'), Uri.parse('')).method, equals('GET'), ); }); test('url is correctly parsed and set in BaseRequest', () { expect( - Request.uri('GET', Uri.parse('https://foo/bar')).url, + Request('GET', Uri.parse('https://foo/bar'), Uri.parse('')).url, equals(Uri.parse('https://foo/bar')), ); expect( - Request.uri('GET', Uri.parse('https://foo/bar?lorem=ipsum&dolor=123')) - .url, + Request( + 'GET', + Uri.parse('https://foo/bar?lorem=ipsum&dolor=123'), + Uri.parse(''), + ).url, equals(Uri.parse('https://foo/bar?lorem=ipsum&dolor=123')), ); expect( - Request.uri( + Request( 'GET', Uri.parse('https://foo/bar'), + Uri.parse(''), parameters: { 'lorem': 'ipsum', 'dolor': 123, @@ -129,11 +138,12 @@ void main() { ); expect( - Request.uri( + Request( 'GET', Uri.parse( 'https://foo/bar?first=sit&second=amet&first_list=a&first_list=b', ), + Uri.parse(''), parameters: { 'lorem': 'ipsum', 'dolor': 123, @@ -144,6 +154,37 @@ void main() { 'https://foo/bar?first=sit&second=amet&first_list=a&first_list=b&lorem=ipsum&dolor=123&second_list=a&second_list=b', )), ); + + expect( + Request( + 'GET', + Uri.parse( + 'https://chopper.dev/test3', + ), + Uri.parse(''), + parameters: { + 'foo': 'bar', + 'foo_list': [ + 'one', + 'two', + 'three', + ], + 'user': { + 'name': 'john', + 'surname': 'doe', + }, + }, + ).url.toString(), + equals( + 'https://chopper.dev/test3' + '?foo=bar' + '&foo_list=one' + '&foo_list=two' + '&foo_list=three' + '&user.name=john' + '&user.surname=doe', + ), + ); }); test('headers are preserved in BaseRequest', () { @@ -152,9 +193,10 @@ void main() { 'accept': 'application/json; charset=utf-8', }; - final Request request = Request.uri( + final Request request = Request( 'GET', Uri.parse('https://foo/bar'), + Uri.parse(''), headers: headers, ); @@ -166,10 +208,67 @@ void main() { test('copyWith creates a BaseRequest', () { expect( - Request.uri('GET', Uri.parse('https://foo/bar')) + Request('GET', Uri.parse('https://foo/bar'), Uri.parse('')) .copyWith(method: HttpMethod.Put), isA(), ); }); }); + + test('request baseUri cannot contain query parameters', () { + expect( + () => Request( + 'GET', + Uri.parse('foo'), + Uri.http( + 'foo', + 'bar', + { + 'first': '123', + 'second': '456', + }, + ), + ), + throwsA( + TypeMatcher(), + ), + ); + + expect( + () => Request( + 'GET', + Uri.parse('foo'), + Uri.parse('foo/bar?first=123'), + ), + throwsA( + TypeMatcher(), + ), + ); + + expect( + () => Request( + 'GET', + Uri.parse('foo'), + Uri( + queryParameters: { + 'first': '123', + 'second': '456', + }, + ), + ), + throwsA( + TypeMatcher(), + ), + ); + expect( + () => Request( + 'GET', + Uri.parse('foo'), + Uri(query: 'first=123&second=456'), + ), + throwsA( + TypeMatcher(), + ), + ); + }); } diff --git a/chopper/test/test_service.chopper.dart b/chopper/test/test_service.chopper.dart index fce9c168..97ce5db3 100644 --- a/chopper/test/test_service.chopper.dart +++ b/chopper/test/test_service.chopper.dart @@ -21,7 +21,7 @@ class _$HttpTestService extends HttpTestService { String id, { required String dynamicHeader, }) { - final String $url = '/test/get/${id}'; + final Uri $url = Uri.parse('/test/get/${id}'); final Map $headers = { 'test': dynamicHeader, }; @@ -36,7 +36,7 @@ class _$HttpTestService extends HttpTestService { @override Future> headTest() { - final String $url = '/test/head'; + final Uri $url = Uri.parse('/test/head'); final Request $request = Request( 'HEAD', $url, @@ -47,7 +47,7 @@ class _$HttpTestService extends HttpTestService { @override Future> optionsTest() { - final String $url = '/test/options'; + final Uri $url = Uri.parse('/test/options'); final Request $request = Request( 'OPTIONS', $url, @@ -58,7 +58,7 @@ class _$HttpTestService extends HttpTestService { @override Future>>> getStreamTest() { - final String $url = '/test/get'; + final Uri $url = Uri.parse('/test/get'); final Request $request = Request( 'GET', $url, @@ -69,7 +69,7 @@ class _$HttpTestService extends HttpTestService { @override Future> getAll() { - final String $url = '/test'; + final Uri $url = Uri.parse('/test'); final Request $request = Request( 'GET', $url, @@ -80,7 +80,7 @@ class _$HttpTestService extends HttpTestService { @override Future> getAllWithTrailingSlash() { - final String $url = '/test/'; + final Uri $url = Uri.parse('/test/'); final Request $request = Request( 'GET', $url, @@ -95,7 +95,7 @@ class _$HttpTestService extends HttpTestService { int? number, int? def = 42, }) { - final String $url = '/test/query'; + final Uri $url = Uri.parse('/test/query'); final Map $params = { 'name': name, 'int': number, @@ -112,7 +112,7 @@ class _$HttpTestService extends HttpTestService { @override Future> getQueryMapTest(Map query) { - final String $url = '/test/query_map'; + final Uri $url = Uri.parse('/test/query_map'); final Map $params = query; final Request $request = Request( 'GET', @@ -128,7 +128,7 @@ class _$HttpTestService extends HttpTestService { Map query, { bool? test, }) { - final String $url = '/test/query_map'; + final Uri $url = Uri.parse('/test/query_map'); final Map $params = {'test': test}; $params.addAll(query); final Request $request = Request( @@ -146,7 +146,7 @@ class _$HttpTestService extends HttpTestService { int? number, Map filters = const {}, }) { - final String $url = '/test/query_map'; + final Uri $url = Uri.parse('/test/query_map'); final Map $params = { 'name': name, 'number': number, @@ -167,7 +167,7 @@ class _$HttpTestService extends HttpTestService { int? number, Map? filters, }) { - final String $url = '/test/query_map'; + final Uri $url = Uri.parse('/test/query_map'); final Map $params = { 'name': name, 'number': number, @@ -184,7 +184,7 @@ class _$HttpTestService extends HttpTestService { @override Future> getQueryMapTest5({Map? filters}) { - final String $url = '/test/query_map'; + final Uri $url = Uri.parse('/test/query_map'); final Map $params = filters ?? const {}; final Request $request = Request( 'GET', @@ -197,7 +197,7 @@ class _$HttpTestService extends HttpTestService { @override Future> getBody(dynamic body) { - final String $url = '/test/get_body'; + final Uri $url = Uri.parse('/test/get_body'); final $body = body; final Request $request = Request( 'GET', @@ -210,7 +210,7 @@ class _$HttpTestService extends HttpTestService { @override Future> postTest(String data) { - final String $url = '/test/post'; + final Uri $url = Uri.parse('/test/post'); final $body = data; final Request $request = Request( 'POST', @@ -223,7 +223,7 @@ class _$HttpTestService extends HttpTestService { @override Future> postStreamTest(Stream> byteStream) { - final String $url = '/test/post'; + final Uri $url = Uri.parse('/test/post'); final $body = byteStream; final Request $request = Request( 'POST', @@ -239,7 +239,7 @@ class _$HttpTestService extends HttpTestService { String test, String data, ) { - final String $url = '/test/put/${test}'; + final Uri $url = Uri.parse('/test/put/${test}'); final $body = data; final Request $request = Request( 'PUT', @@ -252,7 +252,7 @@ class _$HttpTestService extends HttpTestService { @override Future> deleteTest(String id) { - final String $url = '/test/delete/${id}'; + final Uri $url = Uri.parse('/test/delete/${id}'); final Map $headers = { 'foo': 'bar', }; @@ -270,7 +270,7 @@ class _$HttpTestService extends HttpTestService { String id, String data, ) { - final String $url = '/test/patch/${id}'; + final Uri $url = Uri.parse('/test/patch/${id}'); final $body = data; final Request $request = Request( 'PATCH', @@ -283,7 +283,7 @@ class _$HttpTestService extends HttpTestService { @override Future> mapTest(Map map) { - final String $url = '/test/map'; + final Uri $url = Uri.parse('/test/map'); final $body = map; final Request $request = Request( 'POST', @@ -296,7 +296,7 @@ class _$HttpTestService extends HttpTestService { @override Future> postForm(Map fields) { - final String $url = '/test/form/body'; + final Uri $url = Uri.parse('/test/form/body'); final $body = fields; final Request $request = Request( 'POST', @@ -312,7 +312,7 @@ class _$HttpTestService extends HttpTestService { @override Future> postFormUsingHeaders(Map fields) { - final String $url = '/test/form/body'; + final Uri $url = Uri.parse('/test/form/body'); final Map $headers = { 'content-type': 'application/x-www-form-urlencoded', }; @@ -332,7 +332,7 @@ class _$HttpTestService extends HttpTestService { String foo, int bar, ) { - final String $url = '/test/form/body/fields'; + final Uri $url = Uri.parse('/test/form/body/fields'); final $body = { 'foo': foo, 'bar': bar, @@ -351,7 +351,7 @@ class _$HttpTestService extends HttpTestService { @override Future> forceJsonTest(Map map) { - final String $url = '/test/map/json'; + final Uri $url = Uri.parse('/test/map/json'); final $body = map; final Request $request = Request( 'POST', @@ -371,7 +371,7 @@ class _$HttpTestService extends HttpTestService { Map a, Map b, ) { - final String $url = '/test/multi'; + final Uri $url = Uri.parse('/test/multi'); final List $parts = [ PartValue>( '1', @@ -394,7 +394,7 @@ class _$HttpTestService extends HttpTestService { @override Future> postFile(List bytes) { - final String $url = '/test/file'; + final Uri $url = Uri.parse('/test/file'); final List $parts = [ PartValueFile>( 'file', @@ -416,7 +416,7 @@ class _$HttpTestService extends HttpTestService { MultipartFile file, { String? id, }) { - final String $url = '/test/file'; + final Uri $url = Uri.parse('/test/file'); final List $parts = [ PartValue( 'id', @@ -439,7 +439,7 @@ class _$HttpTestService extends HttpTestService { @override Future> postListFiles(List files) { - final String $url = '/test/files'; + final Uri $url = Uri.parse('/test/files'); final List $parts = [ PartValueFile>( 'files', @@ -458,7 +458,7 @@ class _$HttpTestService extends HttpTestService { @override Future fullUrl() { - final String $url = 'https://test.com'; + final Uri $url = Uri.parse('https://test.com'); final Request $request = Request( 'GET', $url, @@ -469,7 +469,7 @@ class _$HttpTestService extends HttpTestService { @override Future>> listString() { - final String $url = '/test/list/string'; + final Uri $url = Uri.parse('/test/list/string'); final Request $request = Request( 'GET', $url, @@ -480,7 +480,7 @@ class _$HttpTestService extends HttpTestService { @override Future> noBody() { - final String $url = '/test/no-body'; + final Uri $url = Uri.parse('/test/no-body'); final Request $request = Request( 'POST', $url, @@ -495,7 +495,7 @@ class _$HttpTestService extends HttpTestService { String? bar, String? baz, }) { - final String $url = '/test/query_param_include_null_query_vars'; + final Uri $url = Uri.parse('/test/query_param_include_null_query_vars'); final Map $params = { 'foo': foo, 'bar': bar, @@ -513,7 +513,7 @@ class _$HttpTestService extends HttpTestService { @override Future> getUsingListQueryParam(List value) { - final String $url = '/test/list_query_param'; + final Uri $url = Uri.parse('/test/list_query_param'); final Map $params = {'value': value}; final Request $request = Request( 'GET', @@ -527,7 +527,7 @@ class _$HttpTestService extends HttpTestService { @override Future> getUsingListQueryParamWithBrackets( List value) { - final String $url = '/test/list_query_param_with_brackets'; + final Uri $url = Uri.parse('/test/list_query_param_with_brackets'); final Map $params = {'value': value}; final Request $request = Request( 'GET', @@ -541,7 +541,7 @@ class _$HttpTestService extends HttpTestService { @override Future> getUsingMapQueryParam(Map value) { - final String $url = '/test/map_query_param'; + final Uri $url = Uri.parse('/test/map_query_param'); final Map $params = {'value': value}; final Request $request = Request( 'GET', @@ -555,7 +555,7 @@ class _$HttpTestService extends HttpTestService { @override Future> getUsingMapQueryParamIncludeNullQueryVars( Map value) { - final String $url = '/test/map_query_param_include_null_query_vars'; + final Uri $url = Uri.parse('/test/map_query_param_include_null_query_vars'); final Map $params = {'value': value}; final Request $request = Request( 'GET', @@ -570,7 +570,7 @@ class _$HttpTestService extends HttpTestService { @override Future> getUsingMapQueryParamWithBrackets( Map value) { - final String $url = '/test/map_query_param_with_brackets'; + final Uri $url = Uri.parse('/test/map_query_param_with_brackets'); final Map $params = {'value': value}; final Request $request = Request( 'GET', diff --git a/chopper_built_value/test/converter_test.dart b/chopper_built_value/test/converter_test.dart index f47cd763..084636d5 100644 --- a/chopper_built_value/test/converter_test.dart +++ b/chopper_built_value/test/converter_test.dart @@ -26,9 +26,10 @@ void main() { group('BuiltValueConverter', () { test('convert request', () { - var request = Request.uri( + var request = Request( HttpMethod.Post, Uri.parse('https://foo/'), + Uri.parse(''), body: data, ); request = converter.convertRequest(request); @@ -69,9 +70,10 @@ void main() { }); test('has json headers', () { - var request = Request.uri( + var request = Request( HttpMethod.Get, Uri.parse('https://foo/'), + Uri.parse(''), body: data, ); request = converter.convertRequest(request); diff --git a/chopper_generator/lib/src/generator.dart b/chopper_generator/lib/src/generator.dart index 2ccd6cd3..67fb905f 100644 --- a/chopper_generator/lib/src/generator.dart +++ b/chopper_generator/lib/src/generator.dart @@ -174,7 +174,7 @@ class ChopperGenerator extends GeneratorForAnnotation { ); final List blocks = [ - declareFinal(_urlVar, type: refer('String')).assign(url).statement, + declareFinal(_urlVar, type: refer('Uri')).assign(url).statement, ]; if (queries.isNotEmpty) { @@ -475,21 +475,26 @@ class ChopperGenerator extends GeneratorForAnnotation { if (path.startsWith('http://') || path.startsWith('https://')) { // if the request's url is already a fully qualified URL, we can use // as-is and ignore the baseUrl - return literal(path); + return _generateUri(path); } else if (path.isEmpty && baseUrl.isEmpty) { - return literal(''); + return _generateUri(''); } else { if (path.isNotEmpty && baseUrl.isNotEmpty && !baseUrl.endsWith('/') && !path.startsWith('/')) { - return literal('$baseUrl/$path'); + return _generateUri('$baseUrl/$path'); } - return literal('$baseUrl$path'); + return _generateUri('$baseUrl$path'); } } + Expression _generateUri(String url) => refer('Uri').newInstanceNamed( + 'parse', + [literal(url)], + ); + Expression _generateRequest( ConstantReader method, { bool hasBody = false, @@ -571,7 +576,9 @@ class ChopperGenerator extends GeneratorForAnnotation { ]; list.add( - refer('PartValueFile<${p.type.getDisplayString(withNullability: p.type.isNullable)}>') + refer('PartValueFile<${p.type.getDisplayString( + withNullability: p.type.isNullable, + )}>') .newInstance(params), ); }); diff --git a/example/bin/main_built_value.dart b/example/bin/main_built_value.dart index 9f87becc..7d7fe8cc 100644 --- a/example/bin/main_built_value.dart +++ b/example/bin/main_built_value.dart @@ -27,7 +27,7 @@ final client = MockClient((req) async { main() async { final chopper = ChopperClient( client: client, - baseUrl: 'http://localhost:8000', + baseUrl: Uri.parse('http://localhost:8000'), converter: BuiltValueConverter(), errorConverter: BuiltValueConverter(), services: [ diff --git a/example/bin/main_json_serializable.dart b/example/bin/main_json_serializable.dart index 956014f5..d95b7f24 100644 --- a/example/bin/main_json_serializable.dart +++ b/example/bin/main_json_serializable.dart @@ -24,7 +24,7 @@ main() async { final chopper = ChopperClient( client: client, - baseUrl: 'http://localhost:8000', + baseUrl: Uri.parse('http://localhost:8000'), // bind your object factories here converter: converter, errorConverter: converter, diff --git a/example/bin/main_json_serializable_squadron_worker_pool.dart b/example/bin/main_json_serializable_squadron_worker_pool.dart index 2c3bbb08..e9564899 100644 --- a/example/bin/main_json_serializable_squadron_worker_pool.dart +++ b/example/bin/main_json_serializable_squadron_worker_pool.dart @@ -126,7 +126,7 @@ Future main() async { final chopper = ChopperClient( client: client, - baseUrl: 'http://localhost:8000', + baseUrl: Uri.parse('http://localhost:8000'), // bind your object factories here converter: converter, errorConverter: converter, diff --git a/example/lib/built_value_resource.chopper.dart b/example/lib/built_value_resource.chopper.dart index a68d4b5c..9b83b3fb 100644 --- a/example/lib/built_value_resource.chopper.dart +++ b/example/lib/built_value_resource.chopper.dart @@ -18,7 +18,7 @@ class _$MyService extends MyService { @override Future> getResource(String id) { - final String $url = '/resources/${id}/'; + final Uri $url = Uri.parse('/resources/${id}/'); final Request $request = Request( 'GET', $url, @@ -29,7 +29,7 @@ class _$MyService extends MyService { @override Future>> getBuiltListResources() { - final String $url = '/resources/list'; + final Uri $url = Uri.parse('/resources/list'); final Request $request = Request( 'GET', $url, @@ -40,7 +40,7 @@ class _$MyService extends MyService { @override Future> getTypedResource() { - final String $url = '/resources/'; + final Uri $url = Uri.parse('/resources/'); final Map $headers = { 'foo': 'bar', }; @@ -58,7 +58,7 @@ class _$MyService extends MyService { Resource resource, { String? name, }) { - final String $url = '/resources'; + final Uri $url = Uri.parse('/resources'); final Map $headers = { if (name != null) 'name': name, }; diff --git a/example/lib/json_serializable.chopper.dart b/example/lib/json_serializable.chopper.dart index e9892bfc..7a60b5ab 100644 --- a/example/lib/json_serializable.chopper.dart +++ b/example/lib/json_serializable.chopper.dart @@ -18,7 +18,7 @@ class _$MyService extends MyService { @override Future> getResource(String id) { - final String $url = '/resources/${id}/'; + final Uri $url = Uri.parse('/resources/${id}/'); final Request $request = Request( 'GET', $url, @@ -29,7 +29,7 @@ class _$MyService extends MyService { @override Future>> getResources() { - final String $url = '/resources/all'; + final Uri $url = Uri.parse('/resources/all'); final Map $headers = { 'test': 'list', }; @@ -44,7 +44,7 @@ class _$MyService extends MyService { @override Future>> getMapResource(String id) { - final String $url = '/resources/'; + final Uri $url = Uri.parse('/resources/'); final Map $params = {'id': id}; final Request $request = Request( 'GET', @@ -57,7 +57,7 @@ class _$MyService extends MyService { @override Future> getTypedResource() { - final String $url = '/resources/'; + final Uri $url = Uri.parse('/resources/'); final Map $headers = { 'foo': 'bar', }; @@ -75,7 +75,7 @@ class _$MyService extends MyService { Resource resource, { String? name, }) { - final String $url = '/resources'; + final Uri $url = Uri.parse('/resources'); final Map $headers = { if (name != null) 'name': name, }; diff --git a/faq.md b/faq.md index fca603c6..f5d2b193 100644 --- a/faq.md +++ b/faq.md @@ -71,8 +71,8 @@ You may need to change the base URL of your network calls during runtime, for ex (Request request) async => SharedPreferences.containsKey('baseUrl') ? request.copyWith( - baseUrl: SharedPreferences.getString('baseUrl')) - : request + baseUri: Uri.parse(SharedPreferences.getString('baseUrl')) + ): request ... ``` @@ -109,7 +109,7 @@ abstract class ApiService extends ChopperService { } return http.Response(json.encode(result), 200); }), - baseUrl: 'https://mysite.com/api', + baseUrl: Uri.parse('https://mysite.com/api'), services: [ _$ApiService(), ], @@ -332,7 +332,7 @@ Future main() async { /// Instantiate a ChopperClient final chopper = ChopperClient( client: client, - baseUrl: 'http://localhost:8000', + baseUrl: Uri.parse('http://localhost:8000'), // bind your object factories here converter: converter, errorConverter: converter,