diff --git a/.github/workflows/qa.yml b/.github/workflows/qa.yml new file mode 100644 index 0000000..51595ca --- /dev/null +++ b/.github/workflows/qa.yml @@ -0,0 +1,23 @@ +name: Quality Assurance + +on: [push] + +jobs: + quality-assurance: + name: QA on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + steps: + - uses: actions/checkout@v1 + - uses: actions/setup-java@v1 + with: + java-version: '12.x' + - uses: subosito/flutter-action@v1.2.0 + with: + flutter-version: '1.11.0' + channel: 'beta' + - run: flutter pub get + - run: flutter test . + - run: flutter analyze . \ No newline at end of file diff --git a/.gitignore b/.gitignore index 238d0e9..1b1102f 100644 --- a/.gitignore +++ b/.gitignore @@ -55,6 +55,7 @@ version **/doc/api/ .dart_tool/ .flutter-plugins +.flutter-plugins-dependencies .packages .pub-cache/ .pub/ diff --git a/CHANGELOG.md b/CHANGELOG.md index c8c5ea6..4ec8838 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,4 +12,15 @@ ## 3.0.5 -- fix: added body in patch and delete methods \ No newline at end of file +- fix: added body in patch and delete methods + +## 3.1.0 + +- BREAKING CHANGE: removed port from url. Added parameter port according to `Uri` best practices. +- feature: added queryParameter support in get requests +- feature: created Github QA workflow to prevent pushes that breaks analyze or tests +- enhancement: changed http method strings into enum to avoid errors +- enhancement: improved type of arguments to prevent unexpected errors +- enhancement: added more validations on tests +- enhancement: executed flutter format to improve pub score +- enhancement: changed some anti patterns. diff --git a/analysis_options.yaml b/analysis_options.yaml index a686c1b..c50a3b7 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -10,5 +10,5 @@ include: package:pedantic/analysis_options.yaml # - camel_case_types analyzer: -# exclude: -# - path/to/excluded/files/** + exclude: + - test/** diff --git a/lib/requests.dart b/lib/requests.dart index fb3cdd2..68935c3 100644 --- a/lib/requests.dart +++ b/lib/requests.dart @@ -1,2 +1,3 @@ library requests; -export 'src/requests.dart'; \ No newline at end of file + +export 'src/requests.dart'; diff --git a/lib/src/requests.dart b/lib/src/requests.dart index e744474..3a2649f 100644 --- a/lib/src/requests.dart +++ b/lib/src/requests.dart @@ -11,6 +11,7 @@ import 'common.dart'; import 'event.dart'; enum RequestBodyEncoding { JSON, FormURLEncoded, PlainText } +enum HttpMethod { GET, PUT, PATCH, POST, DELETE, HEAD } final Logger log = Logger('requests'); @@ -62,12 +63,6 @@ class Requests { const Requests(); static final Event onError = Event(); - static const String HTTP_METHOD_GET = "get"; - static const String HTTP_METHOD_PUT = "put"; - static const String HTTP_METHOD_PATCH = "patch"; - static const String HTTP_METHOD_DELETE = "delete"; - static const String HTTP_METHOD_POST = "post"; - static const String HTTP_METHOD_HEAD = "head"; static const int DEFAULT_TIMEOUT_SECONDS = 10; static const RequestBodyEncoding DEFAULT_BODY_ENCODING = @@ -168,13 +163,15 @@ class Requests { } static Future head(String url, - {headers, - bodyEncoding = DEFAULT_BODY_ENCODING, - timeoutSeconds = DEFAULT_TIMEOUT_SECONDS, - persistCookies = true, - verify = true}) { - return _httpRequest(HTTP_METHOD_HEAD, url, + {Map headers, + int port, + RequestBodyEncoding bodyEncoding = DEFAULT_BODY_ENCODING, + int timeoutSeconds = DEFAULT_TIMEOUT_SECONDS, + bool persistCookies = true, + bool verify = true}) { + return _httpRequest(HttpMethod.HEAD, url, bodyEncoding: bodyEncoding, + port: port, headers: headers, timeoutSeconds: timeoutSeconds, persistCookies: persistCookies, @@ -182,13 +179,17 @@ class Requests { } static Future get(String url, - {headers, - bodyEncoding = DEFAULT_BODY_ENCODING, - timeoutSeconds = DEFAULT_TIMEOUT_SECONDS, - persistCookies = true, - verify = true}) { - return _httpRequest(HTTP_METHOD_GET, url, + {Map headers, + Map queryParameters, + int port, + RequestBodyEncoding bodyEncoding = DEFAULT_BODY_ENCODING, + int timeoutSeconds = DEFAULT_TIMEOUT_SECONDS, + bool persistCookies = true, + bool verify = true}) { + return _httpRequest(HttpMethod.GET, url, bodyEncoding: bodyEncoding, + queryParameters: queryParameters, + port: port, headers: headers, timeoutSeconds: timeoutSeconds, persistCookies: persistCookies, @@ -196,15 +197,17 @@ class Requests { } static Future patch(String url, - {headers, - json, - body, - bodyEncoding = DEFAULT_BODY_ENCODING, - timeoutSeconds = DEFAULT_TIMEOUT_SECONDS, - persistCookies = true, - verify = true}) { - return _httpRequest(HTTP_METHOD_PATCH, url, + {Map headers, + int port, + Map json, + dynamic body, + RequestBodyEncoding bodyEncoding = DEFAULT_BODY_ENCODING, + int timeoutSeconds = DEFAULT_TIMEOUT_SECONDS, + bool persistCookies = true, + bool verify = true}) { + return _httpRequest(HttpMethod.PATCH, url, bodyEncoding: bodyEncoding, + port: port, json: json, body: body, headers: headers, @@ -214,15 +217,19 @@ class Requests { } static Future delete(String url, - {headers, - json, - body, - bodyEncoding = DEFAULT_BODY_ENCODING, - timeoutSeconds = DEFAULT_TIMEOUT_SECONDS, - persistCookies = true, - verify = true}) { - return _httpRequest(HTTP_METHOD_DELETE, url, + {Map headers, + Map json, + Map queryParameters, + dynamic body, + int port, + RequestBodyEncoding bodyEncoding = DEFAULT_BODY_ENCODING, + int timeoutSeconds = DEFAULT_TIMEOUT_SECONDS, + bool persistCookies = true, + bool verify = true}) { + return _httpRequest(HttpMethod.DELETE, url, bodyEncoding: bodyEncoding, + queryParameters: queryParameters, + port: port, json: json, body: body, headers: headers, @@ -232,16 +239,18 @@ class Requests { } static Future post(String url, - {json, - body, - bodyEncoding = DEFAULT_BODY_ENCODING, - headers, - timeoutSeconds = DEFAULT_TIMEOUT_SECONDS, - persistCookies = true, - verify = true}) { - return _httpRequest(HTTP_METHOD_POST, url, + {Map json, + int port, + dynamic body, + RequestBodyEncoding bodyEncoding = DEFAULT_BODY_ENCODING, + Map headers, + int timeoutSeconds = DEFAULT_TIMEOUT_SECONDS, + bool persistCookies = true, + bool verify = true}) { + return _httpRequest(HttpMethod.POST, url, bodyEncoding: bodyEncoding, json: json, + port: port, body: body, headers: headers, timeoutSeconds: timeoutSeconds, @@ -251,17 +260,19 @@ class Requests { static Future put( String url, { - json, - body, - bodyEncoding = DEFAULT_BODY_ENCODING, - headers, - timeoutSeconds = DEFAULT_TIMEOUT_SECONDS, - persistCookies = true, - verify = true, + int port, + Map json, + dynamic body, + RequestBodyEncoding bodyEncoding = DEFAULT_BODY_ENCODING, + Map headers, + int timeoutSeconds = DEFAULT_TIMEOUT_SECONDS, + bool persistCookies = true, + bool verify = true, }) { return _httpRequest( - HTTP_METHOD_PUT, + HttpMethod.PUT, url, + port: port, bodyEncoding: bodyEncoding, json: json, body: body, @@ -272,14 +283,16 @@ class Requests { ); } - static Future _httpRequest(String method, String url, + static Future _httpRequest(HttpMethod method, String url, {json, body, - bodyEncoding = DEFAULT_BODY_ENCODING, - headers, - timeoutSeconds = DEFAULT_TIMEOUT_SECONDS, - persistCookies = true, - verify = true}) async { + RequestBodyEncoding bodyEncoding = DEFAULT_BODY_ENCODING, + Map queryParameters, + int port, + Map headers, + int timeoutSeconds = DEFAULT_TIMEOUT_SECONDS, + bool persistCookies = true, + bool verify = true}) async { http.Client client; if (!verify) { // Ignore SSL errors @@ -306,6 +319,14 @@ class Requests { throw ArgumentError('cannot use both "json" and "body" choose only one.'); } + if (queryParameters != null) { + uri = uri.replace(queryParameters: queryParameters); + } + + if (port != null) { + uri = uri.replace(port: port); + } + if (json != null) { body = json; bodyEncoding = RequestBodyEncoding.JSON; @@ -314,17 +335,19 @@ class Requests { if (body != null) { String contentTypeHeader; - if (bodyEncoding == RequestBodyEncoding.JSON) { - requestBody = Common.toJson(body); - contentTypeHeader = "application/json"; - } else if (bodyEncoding == RequestBodyEncoding.FormURLEncoded) { - requestBody = Common.encodeMap(body); - contentTypeHeader = "application/x-www-form-urlencoded"; - } else if (bodyEncoding == RequestBodyEncoding.PlainText) { - requestBody = body; - contentTypeHeader = "text/plain"; - } else { - throw Exception('unsupported bodyEncoding "$bodyEncoding"'); + switch (bodyEncoding) { + case RequestBodyEncoding.JSON: + requestBody = Common.toJson(body); + contentTypeHeader = "application/json"; + break; + case RequestBodyEncoding.FormURLEncoded: + requestBody = Common.encodeMap(body); + contentTypeHeader = "application/x-www-form-urlencoded"; + break; + case RequestBodyEncoding.PlainText: + requestBody = body; + contentTypeHeader = "text/plain"; + break; } if (contentTypeHeader != null && @@ -333,39 +356,42 @@ class Requests { } } - method = method.toLowerCase(); Future future; + switch (method) { - case HTTP_METHOD_GET: + case HttpMethod.GET: future = client.get(uri, headers: headers); break; - case HTTP_METHOD_PUT: + case HttpMethod.PUT: future = client.put(uri, body: requestBody, headers: headers); break; - case HTTP_METHOD_DELETE: - + case HttpMethod.DELETE: final request = http.Request("DELETE", uri); - requestBody != null ? request.body = requestBody : null; request.headers.addAll(headers); + + if (requestBody != null) { + request.body = requestBody; + } + future = client.send(request); break; - case HTTP_METHOD_POST: + case HttpMethod.POST: future = client.post(uri, body: requestBody, headers: headers); break; - case HTTP_METHOD_HEAD: + case HttpMethod.HEAD: future = client.head(uri, headers: headers); break; - case HTTP_METHOD_PATCH: + case HttpMethod.PATCH: future = client.patch(uri, body: requestBody, headers: headers); break; - default: - throw Exception('unsupported http method "$method"'); } var response = await future.timeout(Duration(seconds: timeoutSeconds)); - if(response is http.StreamedResponse){ - response = await http.Response.fromStream(response); + + if (response is http.StreamedResponse) { + response = await http.Response.fromStream(response); } + return await _handleHttpResponse(hostname, response, persistCookies); } } diff --git a/pubspec.yaml b/pubspec.yaml index 24e804e..12adf09 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,10 +1,11 @@ name: requests description: a flutter library that helps with http requests and stored cookies -version: 3.0.5 +version: 3.1.0 homepage: https://github.com/jossef/requests authors: - Jossef Harush - Rick Stanley +- Rafael Carvalho Monteiro - Martin - Frederik Pietzko environment: diff --git a/test/requests_test.dart b/test/requests_test.dart index 39db877..39198c5 100644 --- a/test/requests_test.dart +++ b/test/requests_test.dart @@ -2,18 +2,42 @@ import 'package:requests/requests.dart'; import 'package:shared_preferences/shared_preferences.dart'; import "package:test/test.dart"; +void _validateBasicWorkingRequest(Response r) { + expect(r.statusCode, isA()); + expect(r.hasError, isA()); + expect(r.success, isA()); + r.raiseForStatus(); +} + void main() { group('A group of tests', () { - const PLACEHOLDER_PROVIDER = 'https://reqres.in'; + final String PLACEHOLDER_PROVIDER = 'https://reqres.in'; + final Map DEFAULT_QUERY_PARAMETER = {'id': '1'}; setUp(() { SharedPreferences.setMockInitialValues({}); }); test('plain http get', () async { var r = await Requests.get("https://google.com"); - r.raiseForStatus(); dynamic body = r.content(); expect(body, isNotNull); + _validateBasicWorkingRequest(r); + }); + + test('plain http get with query parameters', () async { + var r = await Requests.get("https://google.com", + queryParameters: DEFAULT_QUERY_PARAMETER); + dynamic body = r.content(); + expect(body, isNotNull); + expect(r.url.toString(), contains('?id=1')); + _validateBasicWorkingRequest(r); + }); + + test('plain http get with port 80', () async { + var r = await Requests.get("http://google.com", port: 80); + dynamic body = r.content(); + expect(body, isNotNull); + _validateBasicWorkingRequest(r); }); test('json http get list of objects', () async { @@ -24,6 +48,7 @@ void main() { expect(body, isNotNull); expect(body['data'], isList); + _validateBasicWorkingRequest(r); }); test('FormURLEncoded http post', () async { @@ -32,14 +57,22 @@ void main() { "userId": 10, "id": 91, "title": "aut amet sed", - "body": "libero voluptate eveniet aperiam sed\nsunt placeat suscipit molestias\nsimilique fugit nam natus\nexpedita consequatur consequatur dolores quia eos et placeat", + "body": + "libero voluptate eveniet aperiam sed\nsunt placeat suscipit molestias\nsimilique fugit nam natus\nexpedita consequatur consequatur dolores quia eos et placeat", }, bodyEncoding: RequestBodyEncoding.FormURLEncoded); - r.raiseForStatus(); - dynamic body = r.json(); expect(body, isNotNull); + _validateBasicWorkingRequest(r); + }); + + test('json http delete with request body', () async { + var r = await Requests.delete( + "$PLACEHOLDER_PROVIDER/api/users/10", + json: {"something": "something"}, + ); + _validateBasicWorkingRequest(r); }); test('json http post', () async { @@ -47,47 +80,41 @@ void main() { "userId": 10, "id": 91, "title": "aut amet sed", - "body": "libero voluptate eveniet aperiam sed\nsunt placeat suscipit molestias\nsimilique fugit nam natus\nexpedita consequatur consequatur dolores quia eos et placeat", + "body": + "libero voluptate eveniet aperiam sed\nsunt placeat suscipit molestias\nsimilique fugit nam natus\nexpedita consequatur consequatur dolores quia eos et placeat", }); - r.raiseForStatus(); - dynamic body = r.json(); expect(body, isNotNull); + _validateBasicWorkingRequest(r); }); test('json http delete', () async { var r = await Requests.delete("$PLACEHOLDER_PROVIDER/api/users/10"); - r.raiseForStatus(); - }); - - test('json http delete with request body', () async { - var r = await Requests.delete("$PLACEHOLDER_PROVIDER/api/users/10", json: {"something":"something"},); - // I didnt know a better way to test it, since I dont know your mock api... - r.raiseForStatus(); + _validateBasicWorkingRequest(r); }); test('json http post as a form and as a JSON', () async { - var r = await Requests.post("$PLACEHOLDER_PROVIDER/api/users", - json: { - "userId": 10, - "id": 91, - "title": "aut amet sed", - "body": "libero voluptate eveniet aperiam sed\nsunt placeat suscipit molestias\nsimilique fugit nam natus\nexpedita consequatur consequatur dolores quia eos et placeat", - }); - r.raiseForStatus(); + var r = await Requests.post("$PLACEHOLDER_PROVIDER/api/users", json: { + "userId": 10, + "id": 91, + "title": "aut amet sed", + "body": + "libero voluptate eveniet aperiam sed\nsunt placeat suscipit molestias\nsimilique fugit nam natus\nexpedita consequatur consequatur dolores quia eos et placeat", + }); dynamic body = r.json(); expect(body["userId"], 10); + _validateBasicWorkingRequest(r); }); test('json http get object', () async { var r = await Requests.get("$PLACEHOLDER_PROVIDER/api/users/2"); - r.raiseForStatus(); dynamic body = r.json(); expect(body, isNotNull); expect(body, isMap); + _validateBasicWorkingRequest(r); }); test('remove cookies', () async { @@ -104,15 +131,14 @@ void main() { }); test('response as Response object', () async { - var r = await Requests.post('$PLACEHOLDER_PROVIDER/api/users', body: {"name": "morpheus"}); - r.raiseForStatus(); + var r = await Requests.post('$PLACEHOLDER_PROVIDER/api/users', + body: {"name": "morpheus"}); var content = r.content(); var json = r.json(); - expect(r.success, isA()); + _validateBasicWorkingRequest(r); expect(content, isNotNull); expect(json, isNotNull); - expect(r.statusCode, isA()); }); test('throw error', () async { @@ -129,7 +155,8 @@ void main() { test('throw if both json and body used', () async { try { - await Requests.post('$PLACEHOLDER_PROVIDER/api/unknown/23', body: {}, json: {}); + await Requests.post('$PLACEHOLDER_PROVIDER/api/unknown/23', + body: {}, json: {}); } on ArgumentError catch (e) { return; } @@ -151,10 +178,5 @@ void main() { var r = await Requests.get('https://expired.badssl.com/', verify: false); r.raiseForStatus(); }); - - test('http test custom port', () async { - var r = await Requests.get('http://portquiz.net:8080/'); - r.raiseForStatus(); - }); }); }