Tighten argument types to Uri (#507)
Closes #375
Make all arguments that were `Object` in order to allow either `String`
or `Uri` accept only `Uri`. This gives better static checking for
calling code.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e08586b..b88ea78 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,6 +11,8 @@
* Add `const` constructor to `ByteStream`.
* Migrate `BrowserClient` from `blob` to `arraybuffer`.
+* **BREAKING** All APIs which previously allowed a `String` or `Uri` to be
+ passed now require a `Uri`.
* **Breaking** Added a `body` and `encoding` argument to `Client.delete`. This
is only breaking for implementations which override that method.
diff --git a/example/main.dart b/example/main.dart
index e53008f..d6b4997 100644
--- a/example/main.dart
+++ b/example/main.dart
@@ -4,7 +4,8 @@
void main(List<String> arguments) async {
// This example uses the Google Books API to search for books about http.
// https://developers.google.com/books/docs/overview
- var url = 'https://www.googleapis.com/books/v1/volumes?q={http}';
+ var url =
+ Uri.https('www.googleapis.com', '/books/v1/volumes', {'q': '{http}'});
// Await the http get response, then decode the json-formatted response.
var response = await http.get(url);
diff --git a/lib/http.dart b/lib/http.dart
index 7b42071..1ea751e 100644
--- a/lib/http.dart
+++ b/lib/http.dart
@@ -25,30 +25,27 @@
export 'src/streamed_request.dart';
export 'src/streamed_response.dart';
-/// Sends an HTTP HEAD request with the given headers to the given URL, which
-/// can be a [Uri] or a [String].
+/// Sends an HTTP HEAD request with the given headers to the given URL.
///
/// This automatically initializes a new [Client] and closes that client once
/// the request is complete. If you're planning on making multiple requests to
/// the same server, you should use a single [Client] for all of those requests.
///
/// For more fine-grained control over the request, use [Request] instead.
-Future<Response> head(Object url, {Map<String, String>? headers}) =>
+Future<Response> head(Uri url, {Map<String, String>? headers}) =>
_withClient((client) => client.head(url, headers: headers));
-/// Sends an HTTP GET request with the given headers to the given URL, which can
-/// be a [Uri] or a [String].
+/// Sends an HTTP GET request with the given headers to the given URL.
///
/// This automatically initializes a new [Client] and closes that client once
/// the request is complete. If you're planning on making multiple requests to
/// the same server, you should use a single [Client] for all of those requests.
///
/// For more fine-grained control over the request, use [Request] instead.
-Future<Response> get(Object url, {Map<String, String>? headers}) =>
+Future<Response> get(Uri url, {Map<String, String>? headers}) =>
_withClient((client) => client.get(url, headers: headers));
-/// Sends an HTTP POST request with the given headers and body to the given URL,
-/// which can be a [Uri] or a [String].
+/// Sends an HTTP POST request with the given headers and body to the given URL.
///
/// [body] sets the body of the request. It can be a [String], a [List<int>] or
/// a [Map<String, String>]. If it's a String, it's encoded using [encoding] and
@@ -66,13 +63,12 @@
///
/// For more fine-grained control over the request, use [Request] or
/// [StreamedRequest] instead.
-Future<Response> post(Object url,
+Future<Response> post(Uri url,
{Map<String, String>? headers, Object? body, Encoding? encoding}) =>
_withClient((client) =>
client.post(url, headers: headers, body: body, encoding: encoding));
-/// Sends an HTTP PUT request with the given headers and body to the given URL,
-/// which can be a [Uri] or a [String].
+/// Sends an HTTP PUT request with the given headers and body to the given URL.
///
/// [body] sets the body of the request. It can be a [String], a [List<int>] or
/// a [Map<String, String>]. If it's a String, it's encoded using [encoding] and
@@ -90,13 +86,13 @@
///
/// For more fine-grained control over the request, use [Request] or
/// [StreamedRequest] instead.
-Future<Response> put(Object url,
+Future<Response> put(Uri url,
{Map<String, String>? headers, Object? body, Encoding? encoding}) =>
_withClient((client) =>
client.put(url, headers: headers, body: body, encoding: encoding));
/// Sends an HTTP PATCH request with the given headers and body to the given
-/// URL, which can be a [Uri] or a [String].
+/// URL.
///
/// [body] sets the body of the request. It can be a [String], a [List<int>] or
/// a [Map<String, String>]. If it's a String, it's encoded using [encoding] and
@@ -114,27 +110,25 @@
///
/// For more fine-grained control over the request, use [Request] or
/// [StreamedRequest] instead.
-Future<Response> patch(Object url,
+Future<Response> patch(Uri url,
{Map<String, String>? headers, Object? body, Encoding? encoding}) =>
_withClient((client) =>
client.patch(url, headers: headers, body: body, encoding: encoding));
-/// Sends an HTTP DELETE request with the given headers to the given URL, which
-/// can be a [Uri] or a [String].
+/// Sends an HTTP DELETE request with the given headers to the given URL.
///
/// This automatically initializes a new [Client] and closes that client once
/// the request is complete. If you're planning on making multiple requests to
/// the same server, you should use a single [Client] for all of those requests.
///
/// For more fine-grained control over the request, use [Request] instead.
-Future<Response> delete(Object url,
+Future<Response> delete(Uri url,
{Map<String, String>? headers, Object? body, Encoding? encoding}) =>
_withClient((client) =>
client.delete(url, headers: headers, body: body, encoding: encoding));
-/// Sends an HTTP GET request with the given headers to the given URL, which can
-/// be a [Uri] or a [String], and returns a Future that completes to the body of
-/// the response as a [String].
+/// Sends an HTTP GET request with the given headers to the given URL and
+/// returns a Future that completes to the body of the response as a [String].
///
/// The Future will emit a [ClientException] if the response doesn't have a
/// success status code.
@@ -145,12 +139,12 @@
///
/// For more fine-grained control over the request and response, use [Request]
/// instead.
-Future<String> read(Object url, {Map<String, String>? headers}) =>
+Future<String> read(Uri url, {Map<String, String>? headers}) =>
_withClient((client) => client.read(url, headers: headers));
-/// Sends an HTTP GET request with the given headers to the given URL, which can
-/// be a [Uri] or a [String], and returns a Future that completes to the body of
-/// the response as a list of bytes.
+/// Sends an HTTP GET request with the given headers to the given URL and
+/// returns a Future that completes to the body of the response as a list of
+/// bytes.
///
/// The Future will emit a [ClientException] if the response doesn't have a
/// success status code.
@@ -161,7 +155,7 @@
///
/// For more fine-grained control over the request and response, use [Request]
/// instead.
-Future<Uint8List> readBytes(Object url, {Map<String, String>? headers}) =>
+Future<Uint8List> readBytes(Uri url, {Map<String, String>? headers}) =>
_withClient((client) => client.readBytes(url, headers: headers));
Future<T> _withClient<T>(Future<T> Function(Client) fn) async {
diff --git a/lib/src/base_client.dart b/lib/src/base_client.dart
index 52eae4b..efb065f 100644
--- a/lib/src/base_client.dart
+++ b/lib/src/base_client.dart
@@ -19,43 +19,42 @@
/// maybe [close], and then they get various convenience methods for free.
abstract class BaseClient implements Client {
@override
- Future<Response> head(Object url, {Map<String, String>? headers}) =>
+ Future<Response> head(Uri url, {Map<String, String>? headers}) =>
_sendUnstreamed('HEAD', url, headers);
@override
- Future<Response> get(Object url, {Map<String, String>? headers}) =>
+ Future<Response> get(Uri url, {Map<String, String>? headers}) =>
_sendUnstreamed('GET', url, headers);
@override
- Future<Response> post(Object url,
+ Future<Response> post(Uri url,
{Map<String, String>? headers, Object? body, Encoding? encoding}) =>
_sendUnstreamed('POST', url, headers, body, encoding);
@override
- Future<Response> put(Object url,
+ Future<Response> put(Uri url,
{Map<String, String>? headers, Object? body, Encoding? encoding}) =>
_sendUnstreamed('PUT', url, headers, body, encoding);
@override
- Future<Response> patch(Object url,
+ Future<Response> patch(Uri url,
{Map<String, String>? headers, Object? body, Encoding? encoding}) =>
_sendUnstreamed('PATCH', url, headers, body, encoding);
@override
- Future<Response> delete(Object url,
+ Future<Response> delete(Uri url,
{Map<String, String>? headers, Object? body, Encoding? encoding}) =>
_sendUnstreamed('DELETE', url, headers, body, encoding);
@override
- Future<String> read(Object url, {Map<String, String>? headers}) async {
+ Future<String> read(Uri url, {Map<String, String>? headers}) async {
final response = await get(url, headers: headers);
_checkResponseSuccess(url, response);
return response.body;
}
@override
- Future<Uint8List> readBytes(Object url,
- {Map<String, String>? headers}) async {
+ Future<Uint8List> readBytes(Uri url, {Map<String, String>? headers}) async {
final response = await get(url, headers: headers);
_checkResponseSuccess(url, response);
return response.bodyBytes;
@@ -73,9 +72,9 @@
/// Sends a non-streaming [Request] and returns a non-streaming [Response].
Future<Response> _sendUnstreamed(
- String method, url, Map<String, String>? headers,
+ String method, Uri url, Map<String, String>? headers,
[body, Encoding? encoding]) async {
- var request = Request(method, _fromUriOrString(url));
+ var request = Request(method, url);
if (headers != null) request.headers.addAll(headers);
if (encoding != null) request.encoding = encoding;
@@ -95,17 +94,15 @@
}
/// Throws an error if [response] is not successful.
- void _checkResponseSuccess(url, Response response) {
+ void _checkResponseSuccess(Uri url, Response response) {
if (response.statusCode < 400) return;
var message = 'Request to $url failed with status ${response.statusCode}';
if (response.reasonPhrase != null) {
message = '$message: ${response.reasonPhrase}';
}
- throw ClientException('$message.', _fromUriOrString(url));
+ throw ClientException('$message.', url);
}
@override
void close() {}
}
-
-Uri _fromUriOrString(uri) => uri is String ? Uri.parse(uri) : uri as Uri;
diff --git a/lib/src/client.dart b/lib/src/client.dart
index 19c2a88..12695e7 100644
--- a/lib/src/client.dart
+++ b/lib/src/client.dart
@@ -31,20 +31,18 @@
/// `dart:html` is available, otherwise it will throw an unsupported error.
factory Client() => createClient();
- /// Sends an HTTP HEAD request with the given headers to the given URL, which
- /// can be a [Uri] or a [String].
+ /// Sends an HTTP HEAD request with the given headers to the given URL.
///
/// For more fine-grained control over the request, use [send] instead.
- Future<Response> head(Object url, {Map<String, String>? headers});
+ Future<Response> head(Uri url, {Map<String, String>? headers});
- /// Sends an HTTP GET request with the given headers to the given URL, which
- /// can be a [Uri] or a [String].
+ /// Sends an HTTP GET request with the given headers to the given URL.
///
/// For more fine-grained control over the request, use [send] instead.
- Future<Response> get(Object url, {Map<String, String>? headers});
+ Future<Response> get(Uri url, {Map<String, String>? headers});
/// Sends an HTTP POST request with the given headers and body to the given
- /// URL, which can be a [Uri] or a [String].
+ /// URL.
///
/// [body] sets the body of the request. It can be a [String], a [List<int>]
/// or a [Map<String, String>]. If it's a String, it's encoded using
@@ -61,11 +59,11 @@
/// [encoding] defaults to [utf8].
///
/// For more fine-grained control over the request, use [send] instead.
- Future<Response> post(Object url,
+ Future<Response> post(Uri url,
{Map<String, String>? headers, Object? body, Encoding? encoding});
/// Sends an HTTP PUT request with the given headers and body to the given
- /// URL, which can be a [Uri] or a [String].
+ /// URL.
///
/// [body] sets the body of the request. It can be a [String], a [List<int>]
/// or a [Map<String, String>]. If it's a String, it's encoded using
@@ -82,11 +80,11 @@
/// [encoding] defaults to [utf8].
///
/// For more fine-grained control over the request, use [send] instead.
- Future<Response> put(Object url,
+ Future<Response> put(Uri url,
{Map<String, String>? headers, Object? body, Encoding? encoding});
/// Sends an HTTP PATCH request with the given headers and body to the given
- /// URL, which can be a [Uri] or a [String].
+ /// URL.
///
/// [body] sets the body of the request. It can be a [String], a [List<int>]
/// or a [Map<String, String>]. If it's a String, it's encoded using
@@ -103,37 +101,35 @@
/// [encoding] defaults to [utf8].
///
/// For more fine-grained control over the request, use [send] instead.
- Future<Response> patch(Object url,
+ Future<Response> patch(Uri url,
{Map<String, String>? headers, Object? body, Encoding? encoding});
- /// Sends an HTTP DELETE request with the given headers to the given URL,
- /// which can be a [Uri] or a [String].
+ /// Sends an HTTP DELETE request with the given headers to the given URL.
///
/// For more fine-grained control over the request, use [send] instead.
- Future<Response> delete(Object url,
+ Future<Response> delete(Uri url,
{Map<String, String>? headers, Object? body, Encoding? encoding});
- /// Sends an HTTP GET request with the given headers to the given URL, which
- /// can be a [Uri] or a [String], and returns a Future that completes to the
- /// body of the response as a String.
+ /// Sends an HTTP GET request with the given headers to the given URL and
+ /// returns a Future that completes to the body of the response as a String.
///
/// The Future will emit a [ClientException] if the response doesn't have a
/// success status code.
///
/// For more fine-grained control over the request and response, use [send] or
/// [get] instead.
- Future<String> read(Object url, {Map<String, String>? headers});
+ Future<String> read(Uri url, {Map<String, String>? headers});
- /// Sends an HTTP GET request with the given headers to the given URL, which
- /// can be a [Uri] or a [String], and returns a Future that completes to the
- /// body of the response as a list of bytes.
+ /// Sends an HTTP GET request with the given headers to the given URL and
+ /// returns a Future that completes to the body of the response as a list of
+ /// bytes.
///
/// The Future will emit a [ClientException] if the response doesn't have a
/// success status code.
///
/// For more fine-grained control over the request and response, use [send] or
/// [get] instead.
- Future<Uint8List> readBytes(Object url, {Map<String, String>? headers});
+ Future<Uint8List> readBytes(Uri url, {Map<String, String>? headers});
/// Sends an HTTP request and asynchronously returns the response.
Future<StreamedResponse> send(BaseRequest request);
diff --git a/test/mock_client_test.dart b/test/mock_client_test.dart
index 3a18e34..db561c5 100644
--- a/test/mock_client_test.dart
+++ b/test/mock_client_test.dart
@@ -16,7 +16,7 @@
json.encode(request.bodyFields), 200,
request: request, headers: {'content-type': 'application/json'}));
- var response = await client.post('http://example.com/foo',
+ var response = await client.post(Uri.http('example.com', '/foo'),
body: {'field1': 'value1', 'field2': 'value2'});
expect(
response.body, parse(equals({'field1': 'value1', 'field2': 'value2'})));
@@ -30,7 +30,7 @@
return http.StreamedResponse(stream, 200);
});
- var uri = Uri.parse('http://example.com/foo');
+ var uri = Uri.http('example.com', '/foo');
var request = http.Request('POST', uri)..body = 'hello, world';
var streamedResponse = await client.send(request);
var response = await http.Response.fromStream(streamedResponse);
@@ -40,6 +40,7 @@
test('handles a request with no body', () async {
var client = MockClient((_) async => http.Response('you did it', 200));
- expect(await client.read('http://example.com/foo'), equals('you did it'));
+ expect(await client.read(Uri.http('example.com', '/foo')),
+ equals('you did it'));
});
}