Skip to content

Commit

Permalink
Tighten argument types to Uri
Browse files Browse the repository at this point in the history
Fixes #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.
  • Loading branch information
natebosch committed Dec 8, 2020
1 parent d473635 commit 36a0d1a
Show file tree
Hide file tree
Showing 4 changed files with 54 additions and 66 deletions.
44 changes: 19 additions & 25 deletions lib/http.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,30 +25,27 @@ export 'src/response.dart';
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
Expand All @@ -66,13 +63,12 @@ Future<Response> get(Object url, {Map<String, String>? headers}) =>
///
/// 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
Expand All @@ -90,13 +86,13 @@ Future<Response> post(Object url,
///
/// 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
Expand All @@ -114,25 +110,23 @@ Future<Response> put(Object url,
///
/// 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, {Map<String, String>? headers}) =>
Future<Response> delete(Uri url, {Map<String, String>? headers}) =>
_withClient((client) => client.delete(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 [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.
Expand All @@ -143,12 +137,12 @@ Future<Response> delete(Object url, {Map<String, String>? headers}) =>
///
/// 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.
Expand All @@ -159,7 +153,7 @@ Future<String> read(Object url, {Map<String, String>? headers}) =>
///
/// 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 {
Expand Down
27 changes: 12 additions & 15 deletions lib/src/base_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,42 +19,41 @@ import 'streamed_response.dart';
/// 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, {Map<String, String>? headers}) =>
Future<Response> delete(Uri url, {Map<String, String>? headers}) =>
_sendUnstreamed('DELETE', url, headers);

@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;
Expand All @@ -72,9 +71,9 @@ abstract class BaseClient implements Client {

/// 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;
Expand All @@ -94,17 +93,15 @@ abstract class BaseClient implements Client {
}

/// 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;
42 changes: 19 additions & 23 deletions lib/src/client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -31,20 +31,18 @@ abstract class Client {
/// `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
Expand All @@ -61,11 +59,11 @@ abstract class Client {
/// [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
Expand All @@ -82,11 +80,11 @@ abstract class Client {
/// [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
Expand All @@ -103,36 +101,34 @@ abstract class Client {
/// [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, {Map<String, String>? headers});
Future<Response> delete(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 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);
Expand Down
7 changes: 4 additions & 3 deletions test/mock_client_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ void main() {
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'})));
Expand All @@ -30,7 +30,7 @@ void main() {
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);
Expand All @@ -40,6 +40,7 @@ void main() {
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'));
});
}

0 comments on commit 36a0d1a

Please sign in to comment.