From 07c2f4aeffc282e78b1645800a835e67c5e21b07 Mon Sep 17 00:00:00 2001 From: Hugo Pointcheval Date: Thu, 13 Apr 2023 22:00:45 +0200 Subject: [PATCH] refactor(http)!: fix cascade dart good practices + docs --- .../lib/core/dependency_injection/get_it.dart | 14 +- packages/wyatt_http_client/README.md | 36 +- .../example/http_client_example.dart | 294 -------------- .../example/http_client_fastapi_example.dart | 371 ------------------ .../wyatt_http_client/example/pipeline.dart | 164 -------- .../wyatt_http_client/lib/src/middleware.dart | 12 +- .../lib/src/middleware_client.dart | 14 +- .../access_token_auth_middleware.dart | 15 - .../middlewares/basic_auth_middleware.dart | 21 +- .../middlewares/body_to_json_middleware.dart | 11 +- .../src/middlewares/default_middleware.dart | 6 + .../middlewares/digest_auth_middleware.dart | 12 +- .../lib/src/middlewares/middlewares.dart | 3 +- .../refresh_token_auth_middleware.dart | 16 +- .../middlewares/simple_logger_middleware.dart | 40 +- .../middlewares/unsafe_auth_middleware.dart | 15 +- .../middlewares/uri_prefix_middleware.dart | 15 +- .../lib/src/models/middleware_context.dart | 34 +- .../lib/src/models/middleware_request.dart | 49 ++- .../lib/src/models/middleware_response.dart | 26 +- .../lib/src/models/unfreezed_request.dart | 20 +- .../wyatt_http_client/lib/src/pipeline.dart | 27 +- .../lib/src/utils/authentication_methods.dart | 6 + .../lib/src/utils/convert.dart | 12 +- .../lib/src/utils/crypto.dart | 3 +- .../lib/src/utils/delay.dart | 2 + .../lib/src/utils/digest_auth.dart | 5 +- .../lib/src/utils/header_keys.dart | 6 + .../lib/src/utils/http_methods.dart | 4 + .../lib/src/utils/http_status.dart | 6 + .../lib/src/utils/protocols.dart | 4 + .../lib/src/utils/request_utils.dart | 6 + packages/wyatt_http_client/pubspec.yaml | 8 +- 33 files changed, 303 insertions(+), 974 deletions(-) delete mode 100644 packages/wyatt_http_client/example/http_client_example.dart delete mode 100644 packages/wyatt_http_client/example/http_client_fastapi_example.dart delete mode 100644 packages/wyatt_http_client/example/pipeline.dart delete mode 100644 packages/wyatt_http_client/lib/src/middlewares/access_token_auth_middleware.dart diff --git a/packages/wyatt_architecture/example/lib/core/dependency_injection/get_it.dart b/packages/wyatt_architecture/example/lib/core/dependency_injection/get_it.dart index e43a12a1..a3e2bc25 100644 --- a/packages/wyatt_architecture/example/lib/core/dependency_injection/get_it.dart +++ b/packages/wyatt_architecture/example/lib/core/dependency_injection/get_it.dart @@ -52,13 +52,13 @@ abstract class GetItInitializer { ) ..registerLazySingleton(() { final Pipeline pipeline = Pipeline() - .addMiddleware( - UriPrefixMiddleware( - protocol: Protocols.https, - authority: 'jsonplaceholder.typicode.com', - ), - ) - .addMiddleware(BodyToJsonMiddleware()); + ..addMiddleware( + const UriPrefixMiddleware( + protocol: Protocols.https, + authority: 'jsonplaceholder.typicode.com', + ), + ) + ..addMiddleware(const BodyToJsonMiddleware()); return MiddlewareClient(pipeline: pipeline); }) ..registerLazySingleton( diff --git a/packages/wyatt_http_client/README.md b/packages/wyatt_http_client/README.md index ee2b78d5..aee439ed 100644 --- a/packages/wyatt_http_client/README.md +++ b/packages/wyatt_http_client/README.md @@ -16,12 +16,10 @@ * along with this program. If not, see . --> -# Dart - HTTP Client +# HTTP Client

- - Style: Wyatt Analysis - + Style: Wyatt Analysis SDK: Dart & Flutter

@@ -52,13 +50,13 @@ For example, if you want to log every request, and simplify an url you can use p ```dart // Create the Pipeline final Pipeline pipeline = Pipeline() - .addMiddleware( - UriPrefixMiddleware( + ..addMiddleware( + const UriPrefixMiddleware( protocol: Protocols.http, authority: 'localhost:80', ), ) - .addMiddleware(SimpleLoggerMiddleware()); + ..addMiddleware(const SimpleLoggerMiddleware()); ``` Then if you print the pipeline, @@ -94,20 +92,20 @@ Let's start by creating the Pipeline: ```dart final Pipeline pipeline = Pipeline() - .addMiddleware( - UriPrefixMiddleware( + ..addMiddleware( + const UriPrefixMiddleware( protocol: Protocols.http, authority: 'localhost:80', ), ) - .addMiddleware(BodyToJsonMiddleware()) - .addMiddleware( - UnsafeAuthMiddleware( + ..addMiddleware(const BodyToJsonMiddleware()) + ..addMiddleware( + const UnsafeAuthMiddleware( username: 'wyatt', password: 'motdepasse', ), ) - .addMiddleware(SimpleLoggerMiddleware()); + ..addMiddleware(SimpleLoggerMiddleware()); ``` Then simply create a client and make a call. @@ -128,14 +126,14 @@ So now we want a real authentication. ```dart final Pipeline pipeline = Pipeline() - .addMiddleware( - UriPrefixMiddleware( + ..addMiddleware( + const UriPrefixMiddleware( protocol: Protocols.http, authority: 'localhost:80', ), ) - .addMiddleware(BodyToJsonMiddleware()) - .addMiddleware( + ..addMiddleware(const BodyToJsonMiddleware()) + ..addMiddleware( RefreshTokenAuthMiddleware( authorizationEndpoint: '/auth/sign-in', tokenEndpoint: '/auth/refresh', @@ -144,7 +142,7 @@ final Pipeline pipeline = Pipeline() unauthorized: HttpStatus.forbidden, ), ) - .addMiddleware(SimpleLoggerMiddleware()); + ..addMiddleware(const SimpleLoggerMiddleware()); ``` > Here we just change `UnsafeAuthMiddleware` by `RefreshTokenAuthMiddleware` and the whole app while adapt to a new authentication system. @@ -157,6 +155,8 @@ You can create your own middleware by implementing `Middleware` class, and use m class SimpleLoggerMiddleware with OnRequestMiddleware, OnResponseMiddleware implements Middleware { + + const SimpleLoggerMiddleware(); @override String getName() => 'SimpleLogger'; diff --git a/packages/wyatt_http_client/example/http_client_example.dart b/packages/wyatt_http_client/example/http_client_example.dart deleted file mode 100644 index 43009540..00000000 --- a/packages/wyatt_http_client/example/http_client_example.dart +++ /dev/null @@ -1,294 +0,0 @@ -// Copyright (C) 2022 WYATT GROUP -// Please see the AUTHORS file for details. -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -import 'dart:async'; -import 'dart:io'; - -import 'package:wyatt_http_client/wyatt_http_client.dart'; - -String lastToken = ''; -int token = 0; - -void printAuth(HttpRequest req) { - print( - 'Authorization => ' - "${req.headers.value('Authorization') ?? 'no authorization header'}", - ); -} - -Future handleBasic(HttpRequest req) async { - printAuth(req); -} - -Future handleBasicNegotiate(HttpRequest req) async { - if (req.headers.value('Authorization') == null) { - req.response.statusCode = HttpStatus.unauthorized.statusCode; - req.response.headers.set(HeaderKeys.wwwAuthenticate, 'Basic realm="Wyatt"'); - print(req.response.headers.value('WWW-Authenticate')); - return req.response.close(); - } - printAuth(req); -} - -Future handleBearer(HttpRequest req) async { - printAuth(req); -} - -Future handleDigest(HttpRequest req) async { - if (req.headers.value('Authorization') == null) { - req.response.statusCode = HttpStatus.unauthorized.statusCode; - req.response.headers.set( - 'WWW-Authenticate', - 'Digest realm="Wyatt", ' - 'qop="auth,auth-int", ' - 'nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093", ' - 'opaque="5ccc069c403ebaf9f0171e9517f40e41"', - ); - print(req.response.headers.value('WWW-Authenticate')); - return req.response.close(); - } - printAuth(req); -} - -Future handleUnsafe(HttpRequest req) async { - print( - 'Query parameters => ' - '${req.uri.queryParameters}', - ); -} - -Future handleOauth2RefreshToken(HttpRequest req) async { - final action = req.uri.queryParameters['action']; - if (action == null) { - printAuth(req); - } - - switch (action) { - case 'login': - if (req.method == 'POST') { - token++; - req.response.write( - '{"accessToken": "access-token-awesome$token", ' - '"refreshToken": "refresh-token-awesome$token"}', - ); - } - break; - case 'refresh': - printAuth(req); - if (req.method == 'GET') { - token++; - req.response.write('{"accessToken": "access-token-refreshed$token"}'); - } - break; - case 'access-denied': - final String receivedToken = req.headers.value('Authorization') ?? ''; - if (receivedToken != '' && - lastToken != '' && - receivedToken != lastToken) { - lastToken = receivedToken; - printAuth(req); - return req.response.close(); - } else { - lastToken = receivedToken; - req.response.statusCode = HttpStatus.unauthorized.statusCode; - return req.response.close(); - } - default: - break; - } -} - -Future server() async { - final server = await HttpServer.bind(InternetAddress.anyIPv6, 8080); - var error = 0; - var token = 0; - await server.forEach((request) { - print('[${request.method}] ${request.uri}'); - switch (request.uri.path) { - case '/test/basic-test': - handleBasic(request); - break; - case '/test/basic-test-with-negotiate': - handleBasicNegotiate(request); - break; - case '/test/digest-test': - handleDigest(request); - break; - case '/test/apikey-test': - handleBearer(request); - break; - case '/test/bearer-test': - handleBearer(request); - break; - case '/test/unsafe-test': - handleUnsafe(request); - break; - case '/test/oauth2-test': - handleOauth2RefreshToken(request); - break; - - case '/test/bearer-login': - if (request.method == 'POST') { - request.response.write('{"token": "access-token-test"}'); - } - break; - - case '/test/oauth2-test-error': - error++; - print('Error $error'); - if (error >= 3) { - print('Authorized'); - error = 0; - } else { - request.response.statusCode = HttpStatus.unauthorized.statusCode; - } - break; - case '/test/oauth2-test-timeout': - error++; - print('Error $error'); - request.response.statusCode = HttpStatus.unauthorized.statusCode; - break; - case '/test/oauth2-login': - if (request.method == 'POST') { - token++; - request.response.write( - '{"accessToken": "access-token-awesome$token", ' - '"refreshToken": "refresh-token-awesome$token"}', - ); - } - break; - case '/test/oauth2-refresh': - print( - 'Authorization => ' - "${request.headers.value('Authorization') ?? 'no refresh token'}", - ); - if (request.method == 'GET') { - token++; - request.response - .write('{"accessToken": "access-token-refreshed$token"}'); - } - break; - case '/test/oauth2-refresh-error': - request.response.statusCode = HttpStatus.unauthorized.statusCode; - break; - - default: - print(' => Unknown path or method'); - request.response.statusCode = HttpStatus.notFound.statusCode; - } - request.response.close(); - print('===================='); - }); -} - -Future main() async { - unawaited(server()); - const base = 'localhost:8080'; - final uriPrefix = UriPrefixMiddleware( - protocol: Protocols.http, - authority: base, - ); - final jsonEncoder = BodyToJsonMiddleware(); - final logger = SimpleLoggerMiddleware(); - - // Basic - final basicAuth = BasicAuthMiddleware( - username: 'username', - password: 'password', - ); - final basic = MiddlewareClient( - pipeline: Pipeline.fromIterable([ - uriPrefix, - basicAuth, - logger, - ]), - ); - await basic.get(Uri.parse('/test/basic-test')); - - // Digest - final digestAuth = DigestAuthMiddleware( - username: 'Mufasa', - password: 'Circle Of Life', - ); - final digest = MiddlewareClient( - pipeline: Pipeline.fromIterable([ - uriPrefix, - digestAuth, - logger, - ]), - ); - await digest.get(Uri.parse('/test/digest-test')); - - // // Bearer - // final bearer = BearerAuthenticationClient( - // token: 'access-token-test', - // inner: restClient, - // ); - // await bearer.get(Uri.parse('/test/bearer-test')); - - // // API Key - // final apiKey = BearerAuthenticationClient( - // token: 'awesome-api-key', - // authenticationMethod: 'ApiKey', - // inner: restClient, - // ); - // await apiKey.get(Uri.parse('/test/apikey-test')); - - // Unsafe URL - final unsafeAuth = UnsafeAuthMiddleware( - username: 'Mufasa', - password: 'Circle Of Life', - ); - final unsafe = MiddlewareClient( - pipeline: Pipeline.fromIterable([ - uriPrefix, - unsafeAuth, - logger, - ]), - ); - await unsafe.get(Uri.parse('/test/unsafe-test')); - - // OAuth2 - final refreshTokenAuth = RefreshTokenAuthMiddleware( - authorizationEndpoint: '/test/oauth2-test?action=login', - tokenEndpoint: '/test/oauth2-test?action=refresh', - accessTokenParser: (body) => body['accessToken']! as String, - refreshTokenParser: (body) => body['refreshToken']! as String, - ); - final refreshToken = MiddlewareClient( - pipeline: Pipeline.fromIterable([ - uriPrefix, - jsonEncoder, - refreshTokenAuth, - logger, - ]), - ); - await refreshToken.get(Uri.parse('/test/oauth2-test')); - // Login - await refreshToken.post( - Uri.parse('/test/oauth2-test'), - body: { - 'username': 'username', - 'password': 'password', - }, - ); - await refreshToken.get(Uri.parse('/test/oauth2-test')); - // await refreshToken.refresh(); - // await refreshToken.get(Uri.parse('/test/oauth2-test')); - // await refreshToken.get(Uri.parse('/test/oauth2-test?action=access-denied')); - - exit(0); -} diff --git a/packages/wyatt_http_client/example/http_client_fastapi_example.dart b/packages/wyatt_http_client/example/http_client_fastapi_example.dart deleted file mode 100644 index b9a900fd..00000000 --- a/packages/wyatt_http_client/example/http_client_fastapi_example.dart +++ /dev/null @@ -1,371 +0,0 @@ -// Copyright (C) 2022 WYATT GROUP -// Please see the AUTHORS file for details. -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -// ignore_for_file: public_member_api_docs, sort_constructors_first -import 'dart:convert'; - -import 'package:wyatt_http_client/src/middleware_client.dart'; -import 'package:wyatt_http_client/src/middlewares/body_to_json_middleware.dart'; -import 'package:wyatt_http_client/src/middlewares/refresh_token_auth_middleware.dart'; -import 'package:wyatt_http_client/src/middlewares/simple_logger_middleware.dart'; -import 'package:wyatt_http_client/src/middlewares/uri_prefix_middleware.dart'; -import 'package:wyatt_http_client/src/pipeline.dart'; -import 'package:wyatt_http_client/src/utils/http_status.dart'; -import 'package:wyatt_http_client/src/utils/protocols.dart'; - -enum EmailVerificationAction { - signUp, - resetPassword, - changeEmail; - - String toSnakeCase() => name.splitMapJoin( - RegExp('[A-Z]'), - onMatch: (m) => '_${m[0]?.toLowerCase()}', - onNonMatch: (n) => n, - ); - - factory EmailVerificationAction.fromString(String str) => - EmailVerificationAction.values.firstWhere( - (element) => element.toSnakeCase() == str, - ); -} - -class VerifyCode { - final String email; - final String verificationCode; - final EmailVerificationAction action; - VerifyCode({ - required this.email, - required this.verificationCode, - required this.action, - }); - - VerifyCode copyWith({ - String? email, - String? verificationCode, - EmailVerificationAction? action, - }) => - VerifyCode( - email: email ?? this.email, - verificationCode: verificationCode ?? this.verificationCode, - action: action ?? this.action, - ); - - Map toMap() => { - 'email': email, - 'verification_code': verificationCode, - 'action': action.toSnakeCase(), - }; - - factory VerifyCode.fromMap(Map map) => VerifyCode( - email: map['email'] as String, - verificationCode: map['verification_code'] as String, - action: EmailVerificationAction.fromString(map['action'] as String), - ); - - String toJson() => json.encode(toMap()); - - factory VerifyCode.fromJson(String source) => - VerifyCode.fromMap(json.decode(source) as Map); - - @override - String toString() => 'VerifyCode(email: $email, verificationCode: ' - '$verificationCode, action: $action)'; -} - -class Account { - final String email; - final String? sessionId; - Account({ - required this.email, - this.sessionId, - }); - - Account copyWith({ - String? email, - String? sessionId, - }) => - Account( - email: email ?? this.email, - sessionId: sessionId ?? this.sessionId, - ); - - Map toMap() => { - 'email': email, - 'session_id': sessionId, - }; - - factory Account.fromMap(Map map) => Account( - email: map['email'] as String, - sessionId: - map['session_id'] != null ? map['session_id'] as String : null, - ); - - String toJson() => json.encode(toMap()); - - factory Account.fromJson(String source) => - Account.fromMap(json.decode(source) as Map); - - @override - String toString() => 'Account(email: $email, sessionId: $sessionId)'; -} - -class SignUp { - final String sessionId; - final String password; - SignUp({ - required this.sessionId, - required this.password, - }); - - SignUp copyWith({ - String? sessionId, - String? password, - }) => - SignUp( - sessionId: sessionId ?? this.sessionId, - password: password ?? this.password, - ); - - Map toMap() => { - 'session_id': sessionId, - 'password': password, - }; - - factory SignUp.fromMap(Map map) => SignUp( - sessionId: map['session_id'] as String, - password: map['password'] as String, - ); - - String toJson() => json.encode(toMap()); - - factory SignUp.fromJson(String source) => - SignUp.fromMap(json.decode(source) as Map); - - @override - String toString() => 'SignUp(sessionId: $sessionId, password: $password)'; -} - -class TokenSuccess { - final String accessToken; - final String refreshToken; - final Account account; - TokenSuccess({ - required this.accessToken, - required this.refreshToken, - required this.account, - }); - - TokenSuccess copyWith({ - String? accessToken, - String? refreshToken, - Account? account, - }) => - TokenSuccess( - accessToken: accessToken ?? this.accessToken, - refreshToken: refreshToken ?? this.refreshToken, - account: account ?? this.account, - ); - - Map toMap() => { - 'access_token': accessToken, - 'refresh_token': refreshToken, - 'account': account.toMap(), - }; - - factory TokenSuccess.fromMap(Map map) => TokenSuccess( - accessToken: map['access_token'] as String, - refreshToken: map['refresh_token'] as String, - account: Account.fromMap(map['account'] as Map), - ); - - String toJson() => json.encode(toMap()); - - factory TokenSuccess.fromJson(String source) => - TokenSuccess.fromMap(json.decode(source) as Map); - - @override - String toString() => 'TokenSuccess(accessToken: $accessToken, refreshToken: ' - '$refreshToken, account: $account)'; -} - -class Login { - final String email; - final String password; - Login({ - required this.email, - required this.password, - }); - - Login copyWith({ - String? email, - String? password, - }) => - Login( - email: email ?? this.email, - password: password ?? this.password, - ); - - Map toMap() => { - 'email': email, - 'password': password, - }; - - factory Login.fromMap(Map map) => Login( - email: map['email'] as String, - password: map['password'] as String, - ); - - String toJson() => json.encode(toMap()); - - factory Login.fromJson(String source) => - Login.fromMap(json.decode(source) as Map); - - @override - String toString() => 'Login(email: $email, password: $password)'; -} - -class FastAPI { - final String baseUrl; - final MiddlewareClient client; - final int apiVersion; - - FastAPI({ - this.baseUrl = 'localhost:80', - MiddlewareClient? client, - this.apiVersion = 1, - }) : client = client ?? MiddlewareClient(); - - String get apiPath => '/api/v$apiVersion'; - - Future sendSignUpCode(String email) async { - final r = await client.post( - Uri.parse('$apiPath/auth/send-sign-up-code'), - body: { - 'email': email, - }, - ); - if (r.statusCode != 201) { - throw Exception('Invalid reponse: ${r.statusCode}'); - } - } - - Future verifyCode(VerifyCode verifyCode) async { - final r = await client.post( - Uri.parse('$apiPath/auth/verify-code'), - body: verifyCode.toMap(), - ); - if (r.statusCode != 202) { - throw Exception('Invalid reponse: ${r.statusCode}'); - } else { - return Account.fromMap( - (jsonDecode(r.body) as Map)['account'] - as Map, - ); - } - } - - Future signUp(SignUp signUp) async { - final r = await client.post( - Uri.parse('$apiPath/auth/sign-up'), - body: signUp.toMap(), - ); - if (r.statusCode != 201) { - throw Exception('Invalid reponse: ${r.statusCode}'); - } else { - return Account.fromJson(r.body); - } - } - - Future signInWithPassword(Login login) async { - final r = await client.post( - Uri.parse('$apiPath/auth/sign-in-with-password'), - body: login.toMap(), - ); - if (r.statusCode != 200) { - throw Exception('Invalid reponse: ${r.statusCode}'); - } else { - return TokenSuccess.fromJson(r.body); - } - } - - // Future refresh() async { - // final r = await client.refresh(); - // return TokenSuccess.fromJson(r?.body ?? ''); - // } - - Future> getAccountList() async { - final r = await client.get( - Uri.parse('$apiPath/account'), - ); - if (r.statusCode != 200) { - throw Exception('Invalid reponse: ${r.statusCode}'); - } else { - final list = (jsonDecode(r.body) as Map)['founds'] - as List>; - final result = []; - for (final element in list) { - result.add(Account.fromMap(element)); - } - return result; - } - } -} - -void main(List args) async { - final Pipeline pipeline = Pipeline() - .addMiddleware( - UriPrefixMiddleware( - protocol: Protocols.http, - authority: 'localhost:80', - ), - ) - .addMiddleware(BodyToJsonMiddleware()) - .addMiddleware( - RefreshTokenAuthMiddleware( - authorizationEndpoint: '/api/v1/auth/sign-in-with-password', - tokenEndpoint: '/api/v1/auth/refresh', - accessTokenParser: (body) => body['access_token']! as String, - refreshTokenParser: (body) => body['refresh_token']! as String, - unauthorized: HttpStatus.forbidden, - ), - ) - .addMiddleware(SimpleLoggerMiddleware()); - - print(pipeline); - final client = MiddlewareClient(pipeline: pipeline); - - final api = FastAPI( - client: client, - ); - - await api.sendSignUpCode('git@pcl.ovh'); - // final verifiedAccount = await api.verifyCode( - // VerifyCode( - // email: 'git@pcl.ovh', - // verificationCode: '000000000', - // action: EmailVerificationAction.signUp, - // ), - // ); - // final registeredAccount = await api.signUp( - // SignUp(sessionId: verifiedAccount.sessionId ?? '', password: 'password'), - // ); - // final signedInAccount = await api.signInWithPassword( - // Login(email: 'git@pcl.ovh', password: 'password'), - // ); - final accountList = await api.getAccountList(); - print(accountList); -} diff --git a/packages/wyatt_http_client/example/pipeline.dart b/packages/wyatt_http_client/example/pipeline.dart deleted file mode 100644 index 7f261f39..00000000 --- a/packages/wyatt_http_client/example/pipeline.dart +++ /dev/null @@ -1,164 +0,0 @@ -// ignore_for_file: public_member_api_docs, sort_constructors_first -// Copyright (C) 2022 WYATT GROUP -// Please see the AUTHORS file for details. -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -import 'package:wyatt_http_client/src/middleware_client.dart'; -import 'package:wyatt_http_client/src/middlewares/body_to_json_middleware.dart'; -import 'package:wyatt_http_client/src/middlewares/simple_logger_middleware.dart'; -import 'package:wyatt_http_client/src/middlewares/unsafe_auth_middleware.dart'; -import 'package:wyatt_http_client/src/middlewares/uri_prefix_middleware.dart'; -import 'package:wyatt_http_client/src/pipeline.dart'; -import 'package:wyatt_http_client/src/utils/protocols.dart'; - -// class RequestMutatorMiddleware implements Middleware { -// @override -// Middleware? parent; - -// @override -// Middleware? child; - -// RequestMutatorMiddleware({ -// this.parent, -// this.child, -// }); - -// @override -// BaseRequest onRequest(BaseRequest request) { -// print('RequestMutator::OnRequest: ${request.method} -> ${request.url}'); -// return child?.onRequest(request) ?? request; -// } - -// @override -// BaseResponse onResponse(BaseResponse response) { -// final res = child?.onResponse(response) ?? response; -// print( -// 'RequestMutator::OnResponse: ${res.statusCode} -> ${res.contentLength} -// bytes', -// ); -// return res; -// } -// } - -// typedef Middleware = Handler Function(Handler innerHandler); - -// Middleware createMiddleware({ -// FutureOr Function(Request)? requestHandler, -// FutureOr Function(Response)? responseHandler, -// FutureOr Function(Object error, StackTrace)? errorHandler, -// }) { -// requestHandler ??= (request) => null; -// responseHandler ??= (response) => response; - -// FutureOr Function(Object, StackTrace)? onError; -// if (errorHandler != null) { -// onError = (error, stackTrace) { -// if (error is Exception) throw error; -// return errorHandler(error, stackTrace); -// }; -// } - -// return (Handler innerHandler) { -// return (request) { -// return Future.sync(() => requestHandler!(request)).then((response) { -// if (response != null) return response; - -// return Future.sync(() => innerHandler(request)) -// .then((response) => responseHandler!(response), onError: -// onError); -// }); -// }; -// }; -// } - -// extension MiddlewareX on Middleware { -// Middleware addMiddleware(Middleware other) => -// (Handler handler) => this(other(handler)); -// Handler addHandler(Handler handler) => this(handler); -// } - -// typedef Handler = FutureOr Function(Request request); - -// final headerMutator = createMiddleware( -// responseHandler: (response) { -// print(response.headers); -// return response; -// },); - -// class Pipeline { -// const Pipeline(); - -// Pipeline addMiddleware(Middleware middleware) => -// _Pipeline(middleware, addHandler); - -// Handler addHandler(Handler handler) => handler; - -// Middleware get middleware => addHandler; -// } - -// class _Pipeline extends Pipeline { -// final Middleware _middleware; -// final Middleware _parent; - -// _Pipeline(this._middleware, this._parent); - -// @override -// Handler addHandler(Handler handler) => _parent(_middleware(handler)); -// } - -Future main(List args) async { - final UnsafeAuthMiddleware auth = UnsafeAuthMiddleware(); - final Pipeline pipeline = Pipeline() - .addMiddleware( - UriPrefixMiddleware( - protocol: Protocols.http, - authority: 'localhost:80', - ), - ) - .addMiddleware(BodyToJsonMiddleware()) - .addMiddleware( - UnsafeAuthMiddleware( - username: 'wyatt', - password: 'motdepasse', - ), - ) - .addMiddleware(SimpleLoggerMiddleware()); - // .addMiddleware( - // RefreshTokenMiddleware( - // authorizationEndpoint: '/api/v1/account/test?action=authorize', - // tokenEndpoint: '/api/v1/account/test?action=refresh', - // accessTokenParser: (body) => body['access_token']! as String, - // refreshTokenParser: (body) => body['refresh_token']! as String, - // ), - // ); - - print(pipeline); - final client = MiddlewareClient(pipeline: pipeline); - await client.post( - Uri.parse('/api/v1/account/test'), - body: { - 'email': 'test@test.fr', - }, - ); - auth - ..username = 'username' - ..password = 'password'; - await client.post( - Uri.parse('/api/v1/account/test'), - body: { - 'email': 'test@test.fr', - }, - ); -} diff --git a/packages/wyatt_http_client/lib/src/middleware.dart b/packages/wyatt_http_client/lib/src/middleware.dart index 07cf6323..9f0c9d67 100644 --- a/packages/wyatt_http_client/lib/src/middleware.dart +++ b/packages/wyatt_http_client/lib/src/middleware.dart @@ -18,12 +18,21 @@ import 'package:wyatt_http_client/src/models/middleware_context.dart'; import 'package:wyatt_http_client/src/models/middleware_request.dart'; import 'package:wyatt_http_client/src/models/middleware_response.dart'; +/// {@template middleware} +/// A middleware is a class that can intercept requests and responses +/// and modify them before they are sent to the server or before they +/// are returned to the client. +/// {@endtemplate} abstract class Middleware { - Middleware(); + /// {@macro middleware} + const Middleware(); + + /// The name of the middleware. String getName(); } mixin OnRequestMiddleware { + /// Performs an action before the request is sent to the server. Future onRequest( MiddlewareContext context, MiddlewareRequest request, @@ -31,6 +40,7 @@ mixin OnRequestMiddleware { } mixin OnResponseMiddleware { + /// Performs an action before the response is returned to the client. Future onResponse( MiddlewareContext context, MiddlewareResponse response, diff --git a/packages/wyatt_http_client/lib/src/middleware_client.dart b/packages/wyatt_http_client/lib/src/middleware_client.dart index 14cbd094..23c1f064 100644 --- a/packages/wyatt_http_client/lib/src/middleware_client.dart +++ b/packages/wyatt_http_client/lib/src/middleware_client.dart @@ -24,15 +24,23 @@ import 'package:wyatt_http_client/src/models/unfreezed_request.dart'; import 'package:wyatt_http_client/src/pipeline.dart'; import 'package:wyatt_http_client/src/utils/http_methods.dart'; +/// {@template middleware_client} +/// A custom [Client] implementation that allows you to intercept requests +/// and responses and modify them before they are sent to the server or +/// before they are returned to the client. +/// {@endtemplate} class MiddlewareClient extends BaseClient { + /// {@macro middleware_client} MiddlewareClient({ Pipeline? pipeline, Client? inner, }) : pipeline = pipeline ?? Pipeline(), - inner = inner ?? Client() { - print('Using Pipeline:\n$pipeline'); - } + inner = inner ?? Client(); + + /// The [Client] that will be used to send requests. final Client inner; + + /// The [Pipeline] that will be used to intercept requests and responses. final Pipeline pipeline; @override diff --git a/packages/wyatt_http_client/lib/src/middlewares/access_token_auth_middleware.dart b/packages/wyatt_http_client/lib/src/middlewares/access_token_auth_middleware.dart deleted file mode 100644 index 1bb8b149..00000000 --- a/packages/wyatt_http_client/lib/src/middlewares/access_token_auth_middleware.dart +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (C) 2022 WYATT GROUP -// Please see the AUTHORS file for details. -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . diff --git a/packages/wyatt_http_client/lib/src/middlewares/basic_auth_middleware.dart b/packages/wyatt_http_client/lib/src/middlewares/basic_auth_middleware.dart index 529e3113..37bf033a 100644 --- a/packages/wyatt_http_client/lib/src/middlewares/basic_auth_middleware.dart +++ b/packages/wyatt_http_client/lib/src/middlewares/basic_auth_middleware.dart @@ -22,14 +22,24 @@ import 'package:wyatt_http_client/src/models/middleware_request.dart'; import 'package:wyatt_http_client/src/utils/authentication_methods.dart'; import 'package:wyatt_http_client/src/utils/header_keys.dart'; +/// {@template basic_auth_middleware} +/// A middleware that adds basic authentication to the request. +/// {@endtemplate} class BasicAuthMiddleware with OnRequestMiddleware implements Middleware { - BasicAuthMiddleware({ + /// {@macro basic_auth_middleware} + const BasicAuthMiddleware({ this.username, this.password, this.authenticationHeader = HeaderKeys.authorization, }); - String? username; - String? password; + + /// The username to use for authentication. + final String? username; + + /// The password to use for authentication. + final String? password; + + /// The header to use for authentication. final String authenticationHeader; @override @@ -43,10 +53,7 @@ class BasicAuthMiddleware with OnRequestMiddleware implements Middleware { if (username == null || password == null) { return request; } - print( - '${getName()}::OnRequest\n' - '>> Basic: ${base64Encode(utf8.encode('$username:$password'))}', - ); + final mutation = { authenticationHeader: '${AuthenticationMethods.basic} ' '${base64Encode(utf8.encode('$username:$password'))}', diff --git a/packages/wyatt_http_client/lib/src/middlewares/body_to_json_middleware.dart b/packages/wyatt_http_client/lib/src/middlewares/body_to_json_middleware.dart index a77048d8..e9aaaba7 100644 --- a/packages/wyatt_http_client/lib/src/middlewares/body_to_json_middleware.dart +++ b/packages/wyatt_http_client/lib/src/middlewares/body_to_json_middleware.dart @@ -20,7 +20,13 @@ import 'package:wyatt_http_client/src/middleware.dart'; import 'package:wyatt_http_client/src/models/middleware_context.dart'; import 'package:wyatt_http_client/src/models/middleware_request.dart'; +/// {@template body_to_json_middleware} +/// A middleware that transforms the body in json if it's a [Map]. +/// {@endtemplate} class BodyToJsonMiddleware with OnRequestMiddleware implements Middleware { + /// {@macro body_to_json_middleware} + const BodyToJsonMiddleware(); + @override String getName() => 'BodyToJson'; @@ -29,11 +35,6 @@ class BodyToJsonMiddleware with OnRequestMiddleware implements Middleware { MiddlewareContext context, MiddlewareRequest request, ) async { - print( - '${getName()}::OnRequest\n' - '>> Transforms body in json if Map then update ' - 'headers with right content-type', - ); final mutation = { 'content-type': 'application/json; charset=utf-8', }; diff --git a/packages/wyatt_http_client/lib/src/middlewares/default_middleware.dart b/packages/wyatt_http_client/lib/src/middlewares/default_middleware.dart index 65b766ca..456a07b5 100644 --- a/packages/wyatt_http_client/lib/src/middlewares/default_middleware.dart +++ b/packages/wyatt_http_client/lib/src/middlewares/default_middleware.dart @@ -16,7 +16,13 @@ import 'package:wyatt_http_client/src/middleware.dart'; +/// {@template default_middleware} +/// A default middleware that does nothing. +/// {@endtemplate} class DefaultMiddleware implements Middleware { + /// {@macro default_middleware} + const DefaultMiddleware(); + @override String getName() => 'DefaultMiddleware'; } diff --git a/packages/wyatt_http_client/lib/src/middlewares/digest_auth_middleware.dart b/packages/wyatt_http_client/lib/src/middlewares/digest_auth_middleware.dart index c09d0423..55c17030 100644 --- a/packages/wyatt_http_client/lib/src/middlewares/digest_auth_middleware.dart +++ b/packages/wyatt_http_client/lib/src/middlewares/digest_auth_middleware.dart @@ -22,9 +22,13 @@ import 'package:wyatt_http_client/src/utils/digest_auth.dart'; import 'package:wyatt_http_client/src/utils/header_keys.dart'; import 'package:wyatt_http_client/src/utils/http_status.dart'; +/// {@template digest_auth_middleware} +/// A middleware that handles digest authentication. +/// {@endtemplate} class DigestAuthMiddleware with OnRequestMiddleware, OnResponseMiddleware implements Middleware { + /// {@macro digest_auth_middleware} DigestAuthMiddleware({ required this.username, required this.password, @@ -47,10 +51,6 @@ class DigestAuthMiddleware MiddlewareContext context, MiddlewareRequest request, ) async { - print( - '${getName()}::OnRequest\n' - '>> Digest ready: ${_digestAuth.isReady()}', - ); if (_digestAuth.isReady()) { final mutation = { authenticationHeader: _digestAuth.getAuthString( @@ -82,10 +82,6 @@ class DigestAuthMiddleware return MiddlewareResponse(httpResponse: newResponse); } } - print( - '${getName()}::OnResponse\n' - '>> Digest ready: ${_digestAuth.isReady()}', - ); return response; } } diff --git a/packages/wyatt_http_client/lib/src/middlewares/middlewares.dart b/packages/wyatt_http_client/lib/src/middlewares/middlewares.dart index 9c2b70b0..29bbf147 100644 --- a/packages/wyatt_http_client/lib/src/middlewares/middlewares.dart +++ b/packages/wyatt_http_client/lib/src/middlewares/middlewares.dart @@ -14,7 +14,8 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -export 'access_token_auth_middleware.dart'; +// All built-in middlewares + export 'basic_auth_middleware.dart'; export 'body_to_json_middleware.dart'; export 'default_middleware.dart'; diff --git a/packages/wyatt_http_client/lib/src/middlewares/refresh_token_auth_middleware.dart b/packages/wyatt_http_client/lib/src/middlewares/refresh_token_auth_middleware.dart index 19f3f137..50f5005d 100644 --- a/packages/wyatt_http_client/lib/src/middlewares/refresh_token_auth_middleware.dart +++ b/packages/wyatt_http_client/lib/src/middlewares/refresh_token_auth_middleware.dart @@ -28,9 +28,14 @@ import 'package:wyatt_http_client/src/utils/http_status.dart'; typedef TokenParser = String Function(Map); +/// {@template refresh_token_auth_middleware} +/// A middleware that refreshes the access token when it expires. +/// This middleware is useful for OAuth2. +/// {@endtemplate} class RefreshTokenAuthMiddleware with OnRequestMiddleware, OnResponseMiddleware implements Middleware { + /// {@macro refresh_token_auth_middleware} RefreshTokenAuthMiddleware({ required this.authorizationEndpoint, required this.tokenEndpoint, @@ -113,11 +118,6 @@ class RefreshTokenAuthMiddleware MiddlewareContext context, MiddlewareRequest request, ) async { - print( - '${getName()}::OnRequest\n' - '>> accessToken: $accessToken\n' - '>> refreshToken: $refreshToken', - ); // Check if it is authorization if (context.originalRequest?.url == Uri.parse(authorizationEndpoint)) { return request; @@ -168,12 +168,6 @@ class RefreshTokenAuthMiddleware } } - print( - '${getName()}::OnResponse\n' - '>> accessToken: $accessToken\n' - '>> refreshToken: $refreshToken', - ); - if (response.status == unauthorized) { // Refresh MiddlewareRequest? newRequest = await refresh(context); diff --git a/packages/wyatt_http_client/lib/src/middlewares/simple_logger_middleware.dart b/packages/wyatt_http_client/lib/src/middlewares/simple_logger_middleware.dart index 8e2d2637..31492b8f 100644 --- a/packages/wyatt_http_client/lib/src/middlewares/simple_logger_middleware.dart +++ b/packages/wyatt_http_client/lib/src/middlewares/simple_logger_middleware.dart @@ -19,9 +19,15 @@ import 'package:wyatt_http_client/src/models/middleware_context.dart'; import 'package:wyatt_http_client/src/models/middleware_request.dart'; import 'package:wyatt_http_client/src/models/middleware_response.dart'; +/// {@template simple_logger_middleware} +/// A simple logger middleware that logs the request and response. +/// {@endtemplate} class SimpleLoggerMiddleware with OnRequestMiddleware, OnResponseMiddleware implements Middleware { + /// {@macro simple_logger_middleware} + const SimpleLoggerMiddleware(); + @override String getName() => 'SimpleLogger'; @@ -30,11 +36,22 @@ class SimpleLoggerMiddleware MiddlewareContext context, MiddlewareRequest request, ) async { - print( - '${getName()}::OnRequest\n' - '>> ${request.method} ${request.url}\n' - '>> Headers: ${request.headers}\n>> Body: ${request.encodedBody}', - ); + final log = StringBuffer() + ..writeln('${getName()}::OnRequest') + ..writeln('>> ${request.method} ${request.url}'); + if (request.headers.isNotEmpty) { + log.writeln('>> Headers:'); + request.headers.forEach((key, value) { + log.writeln('>> $key: $value'); + }); + } + if (request.encodedBody.isNotEmpty) { + log + ..writeln('>> Body:') + ..writeln(request.encodedBody); + } + print(log); + return request; } @@ -43,12 +60,13 @@ class SimpleLoggerMiddleware MiddlewareContext context, MiddlewareResponse response, ) async { - print( - '${getName()}::OnResponse\n' - '>> Status: ${response.status.name.toUpperCase()}\n' - '>> Length: ${response.contentLength ?? '0'} bytes', - // '>> Body: ${response.body}', - ); + final log = StringBuffer() + ..writeln('${getName()}::OnResponse') + ..writeln('>> Status: ${response.status.name.toUpperCase()}') + ..writeln('>> Length: ${response.contentLength ?? '0'} bytes'); + + print(log); + return response; } } diff --git a/packages/wyatt_http_client/lib/src/middlewares/unsafe_auth_middleware.dart b/packages/wyatt_http_client/lib/src/middlewares/unsafe_auth_middleware.dart index 724d7211..f4481877 100644 --- a/packages/wyatt_http_client/lib/src/middlewares/unsafe_auth_middleware.dart +++ b/packages/wyatt_http_client/lib/src/middlewares/unsafe_auth_middleware.dart @@ -19,15 +19,20 @@ import 'package:wyatt_http_client/src/models/middleware_context.dart'; import 'package:wyatt_http_client/src/models/middleware_request.dart'; import 'package:wyatt_http_client/src/utils/convert.dart'; +/// {@template unsafe_auth_middleware} +/// A middleware that appends the username and password to the URL. +/// +/// This is not recommended to use in production. +/// {@endtemplate} class UnsafeAuthMiddleware with OnRequestMiddleware implements Middleware { - UnsafeAuthMiddleware({ + const UnsafeAuthMiddleware({ this.username, this.password, this.usernameField = 'username', this.passwordField = 'password', }); - String? username; - String? password; + final String? username; + final String? password; final String usernameField; final String passwordField; @@ -45,10 +50,6 @@ class UnsafeAuthMiddleware with OnRequestMiddleware implements Middleware { } final Uri uri = request.url + '?$usernameField=$username&$passwordField=$password'; - print( - '${getName()}::OnRequest\n' - '>> Append: ?$usernameField=$username&$passwordField=$password', - ); request.modifyRequest(request.unfreezedRequest.copyWith(url: uri)); return request; } diff --git a/packages/wyatt_http_client/lib/src/middlewares/uri_prefix_middleware.dart b/packages/wyatt_http_client/lib/src/middlewares/uri_prefix_middleware.dart index 04c3232f..f02cff99 100644 --- a/packages/wyatt_http_client/lib/src/middlewares/uri_prefix_middleware.dart +++ b/packages/wyatt_http_client/lib/src/middlewares/uri_prefix_middleware.dart @@ -19,12 +19,20 @@ import 'package:wyatt_http_client/src/models/middleware_context.dart'; import 'package:wyatt_http_client/src/models/middleware_request.dart'; import 'package:wyatt_http_client/src/utils/protocols.dart'; +/// {@template uri_prefix_middleware} +/// A middleware that adds a prefix to the request's URI. +/// {@endtemplate} class UriPrefixMiddleware with OnRequestMiddleware implements Middleware { - UriPrefixMiddleware({ + /// {@macro uri_prefix_middleware} + const UriPrefixMiddleware({ required this.protocol, required this.authority, }); + + /// The protocol of the prefix. final Protocols protocol; + + /// The authority of the prefix. final String? authority; @override @@ -36,11 +44,6 @@ class UriPrefixMiddleware with OnRequestMiddleware implements Middleware { MiddlewareRequest request, ) async { final Uri uri = Uri.parse('${protocol.scheme}$authority${request.url}'); - print( - '${getName()}::OnRequest\n' - '>> From: ${request.url}\n' - '>> To: $uri', - ); request.modifyRequest(request.unfreezedRequest.copyWith(url: uri)); return request; } diff --git a/packages/wyatt_http_client/lib/src/models/middleware_context.dart b/packages/wyatt_http_client/lib/src/models/middleware_context.dart index 503c2e3e..5071b39e 100644 --- a/packages/wyatt_http_client/lib/src/models/middleware_context.dart +++ b/packages/wyatt_http_client/lib/src/models/middleware_context.dart @@ -1,4 +1,3 @@ -// ignore_for_file: public_member_api_docs, sort_constructors_first // Copyright (C) 2022 WYATT GROUP // Please see the AUTHORS file for details. // @@ -20,15 +19,12 @@ import 'package:wyatt_http_client/src/models/middleware_request.dart'; import 'package:wyatt_http_client/src/models/middleware_response.dart'; import 'package:wyatt_http_client/src/pipeline.dart'; +/// {@template middleware_context} +/// A class that contains the context of the middleware. +/// {@endtemplate} class MiddlewareContext { - Pipeline pipeline; - MiddlewareClient client; - MiddlewareRequest? originalRequest; - MiddlewareRequest? lastRequest; - MiddlewareResponse? originalResponse; - MiddlewareResponse? lastResponse; - - MiddlewareContext({ + /// {@macro middleware_context} + const MiddlewareContext({ required this.pipeline, required this.client, this.originalRequest, @@ -37,6 +33,26 @@ class MiddlewareContext { this.lastResponse, }); + /// The pipeline that the middleware is in. + final Pipeline pipeline; + + /// The client that the middleware is in. + final MiddlewareClient client; + + /// The original request that the middleware is in. + final MiddlewareRequest? originalRequest; + + /// The last request that the middleware is in. + final MiddlewareRequest? lastRequest; + + /// The original response that the middleware is in. + final MiddlewareResponse? originalResponse; + + /// The last response that the middleware is in. + final MiddlewareResponse? lastResponse; + + /// Create a copy of this [MiddlewareContext] with the given fields replaced + /// with the new values. MiddlewareContext copyWith({ Pipeline? pipeline, MiddlewareClient? client, diff --git a/packages/wyatt_http_client/lib/src/models/middleware_request.dart b/packages/wyatt_http_client/lib/src/models/middleware_request.dart index 333d3177..e7c93703 100644 --- a/packages/wyatt_http_client/lib/src/models/middleware_request.dart +++ b/packages/wyatt_http_client/lib/src/models/middleware_request.dart @@ -1,4 +1,3 @@ -// ignore_for_file: public_member_api_docs, sort_constructors_first // Copyright (C) 2022 WYATT GROUP // Please see the AUTHORS file for details. // @@ -22,24 +21,43 @@ import 'package:wyatt_http_client/src/models/unfreezed_request.dart'; import 'package:wyatt_http_client/src/utils/convert.dart'; import 'package:wyatt_http_client/src/utils/request_utils.dart'; +/// {@template middleware_request} +/// A class that represents a middleware request. +/// {@endtemplate} class MiddlewareRequest { - UnfreezedRequest unfreezedRequest; - Request _httpRequest; - - Request get request => _httpRequest; - - // Proxy - String get method => _httpRequest.method; - Uri get url => _httpRequest.url; - Map get headers => _httpRequest.headers; - Encoding get encoding => _httpRequest.encoding; - String get encodedBody => _httpRequest.body; - Object? get body => unfreezedRequest.body; - + /// {@macro middleware_request} MiddlewareRequest({ required this.unfreezedRequest, }) : _httpRequest = Request(unfreezedRequest.method, unfreezedRequest.url); + /// The unfreezed request. + UnfreezedRequest unfreezedRequest; + + Request _httpRequest; + + /// The http request. (Read-only) + Request get request => _httpRequest; + + /// The request method (proxy, read-only). + String get method => _httpRequest.method; + + /// The request url (proxy, read-only). + Uri get url => _httpRequest.url; + + /// The request headers (proxy, read-only). + Map get headers => _httpRequest.headers; + + /// The request body (proxy, read-only). + Encoding get encoding => _httpRequest.encoding; + + /// The request body (proxy, read-only). + String get encodedBody => _httpRequest.body; + + /// The request body (proxy, read-only). + Object? get body => unfreezedRequest.body; + + /// Copies this request and returns a new request with the given + /// [unfreezedRequest]. MiddlewareRequest copyWith({ UnfreezedRequest? unfreezedRequest, }) => @@ -47,6 +65,7 @@ class MiddlewareRequest { unfreezedRequest: unfreezedRequest ?? this.unfreezedRequest, ); + /// Modifies the request with the given [unfreezedRequest]. void modifyRequest(UnfreezedRequest unfreezedRequest) { String? body; if (unfreezedRequest.body != null) { @@ -72,6 +91,8 @@ class MiddlewareRequest { this.unfreezedRequest = unfreezedRequest; } + /// Applies the changes made to the request by modifying it with the + /// [unfreezedRequest]. void apply() { modifyRequest(unfreezedRequest); } diff --git a/packages/wyatt_http_client/lib/src/models/middleware_response.dart b/packages/wyatt_http_client/lib/src/models/middleware_response.dart index 500c17c3..19781901 100644 --- a/packages/wyatt_http_client/lib/src/models/middleware_response.dart +++ b/packages/wyatt_http_client/lib/src/models/middleware_response.dart @@ -1,4 +1,3 @@ -// ignore_for_file: public_member_api_docs, sort_constructors_first // Copyright (C) 2022 WYATT GROUP // Please see the AUTHORS file for details. // @@ -18,12 +17,25 @@ import 'package:http/http.dart'; import 'package:wyatt_http_client/src/utils/http_status.dart'; +/// {@template middleware_response} +/// A class that represents a middleware response. +/// {@endtemplate} class MiddlewareResponse { - BaseResponse httpResponse; + /// {@macro middleware_response} + const MiddlewareResponse({ + required this.httpResponse, + }); - // Proxy + /// {@macro middleware_response} + final BaseResponse httpResponse; + + /// The status code of the response. (proxy) int get statusCode => httpResponse.statusCode; + + /// The status of the response. (proxy) HttpStatus get status => HttpStatus.from(statusCode); + + /// The body of the response. (proxy or empty string) String get body { if (httpResponse is Response) { return (httpResponse as Response).body; @@ -32,13 +44,13 @@ class MiddlewareResponse { } } + /// The content length of the response. (proxy) int? get contentLength => httpResponse.contentLength; + + /// The headers of the response. (proxy) Map get headers => httpResponse.headers; - MiddlewareResponse({ - required this.httpResponse, - }); - + /// Returns a copy of this response with the given [httpResponse]. MiddlewareResponse copyWith({ BaseResponse? httpResponse, }) => diff --git a/packages/wyatt_http_client/lib/src/models/unfreezed_request.dart b/packages/wyatt_http_client/lib/src/models/unfreezed_request.dart index d29c15d8..6779a5b6 100644 --- a/packages/wyatt_http_client/lib/src/models/unfreezed_request.dart +++ b/packages/wyatt_http_client/lib/src/models/unfreezed_request.dart @@ -16,20 +16,38 @@ import 'dart:convert'; +/// {@template unfreezed_request} +/// A class that represents an unfreezed request. +/// It is used to unfreeze a Request object, and allows you to +/// modify the request before sending it. +/// {@endtemplate} class UnfreezedRequest { - UnfreezedRequest({ + /// {@macro unfreezed_request} + const UnfreezedRequest({ required this.method, required this.url, this.headers, this.body, this.encoding, }); + + /// The request method. final String method; + + /// The request url. final Uri url; + + /// The request headers. final Map? headers; + + /// The request body. final Object? body; + + /// The request encoding. final Encoding? encoding; + /// Copies this request and returns a new request with the given [method], + /// [url], [headers], [body] and [encoding]. UnfreezedRequest copyWith({ String? method, Uri? url, diff --git a/packages/wyatt_http_client/lib/src/pipeline.dart b/packages/wyatt_http_client/lib/src/pipeline.dart index 3cebc400..7e1c2900 100644 --- a/packages/wyatt_http_client/lib/src/pipeline.dart +++ b/packages/wyatt_http_client/lib/src/pipeline.dart @@ -19,20 +19,27 @@ import 'package:wyatt_http_client/src/models/middleware_context.dart'; import 'package:wyatt_http_client/src/models/middleware_request.dart'; import 'package:wyatt_http_client/src/models/middleware_response.dart'; +/// {@template pipeline} +/// A [Pipeline] is a list of [Middleware]s that are executed in order. +/// {@endtemplate} class Pipeline { + /// {@macro pipeline} Pipeline() : _middlewares = []; + + /// {@macro pipeline} Pipeline.fromIterable(Iterable middlewares) : _middlewares = middlewares.toList(); + final List _middlewares; + /// The length of the [Pipeline]. + /// + /// This is the number of [Middleware]s in the [Pipeline]. int get length => _middlewares.length; /// Add a [Middleware] to this [Pipeline] - Pipeline addMiddleware(Middleware middleware) { + void addMiddleware(Middleware middleware) { _middlewares.add(middleware); - // TODO(hpcl): use Dart cascades instead of returning this - // ignore: avoid_returning_this - return this; } /// Create new [Pipeline] from the start or end to a specified [Middleware]. @@ -57,11 +64,15 @@ class Pipeline { return Pipeline.fromIterable(fromEnd ? nodes.reversed : nodes); } + /// Call the [onRequest] method of all [OnRequestMiddleware]s in the + /// [Pipeline]. + /// + /// The [MiddlewareRequest] returned by the last [OnRequestMiddleware] is + /// returned. Future onRequest( MiddlewareContext context, MiddlewareRequest request, ) async { - print('\n\nNEW REQUEST\n'); MiddlewareRequest req = request..apply(); MiddlewareContext ctx = context.copyWith(lastRequest: req); for (final middleware in _middlewares) { @@ -73,11 +84,15 @@ class Pipeline { return req; } + /// Call the [onResponse] method of all [OnResponseMiddleware]s in the + /// [Pipeline]. + /// + /// The [MiddlewareResponse] returned by the last [OnResponseMiddleware] is + /// returned. Future onResponse( MiddlewareContext context, MiddlewareResponse response, ) async { - print('\n\nNEW RESPONSE\n'); MiddlewareResponse res = response; MiddlewareContext ctx = context.copyWith(lastResponse: res); for (final middleware in _middlewares.reversed) { diff --git a/packages/wyatt_http_client/lib/src/utils/authentication_methods.dart b/packages/wyatt_http_client/lib/src/utils/authentication_methods.dart index ef73c15f..d8f028c2 100644 --- a/packages/wyatt_http_client/lib/src/utils/authentication_methods.dart +++ b/packages/wyatt_http_client/lib/src/utils/authentication_methods.dart @@ -14,8 +14,14 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +/// Defines some authentication methods abstract class AuthenticationMethods { + /// The `Basic` authentication method. static const String basic = 'Basic'; + + /// The `Bearer` authentication method. static const String bearer = 'Bearer'; + + /// The `Digest` authentication method. static const String digest = 'Digest'; } diff --git a/packages/wyatt_http_client/lib/src/utils/convert.dart b/packages/wyatt_http_client/lib/src/utils/convert.dart index 1f287757..93c86d59 100644 --- a/packages/wyatt_http_client/lib/src/utils/convert.dart +++ b/packages/wyatt_http_client/lib/src/utils/convert.dart @@ -16,7 +16,11 @@ import 'dart:convert'; -class Convert { +/// Defines some convert functions. +abstract class Convert { + /// Converts a list of bytes to a hex string. + /// + /// If [upperCase] is `true`, the hex string will be in uppercase. static String toHex(List bytes, {bool upperCase = false}) { final buffer = StringBuffer(); for (final int part in bytes) { @@ -32,6 +36,11 @@ class Convert { } } + /// Converts a map to a query string. + /// + /// If [encoding] is `null`, the default encoding is `utf8`. + /// + /// For example, the map `{a: 1, b: 2}` will be converted to `a=1&b=2`. static String mapToQuery(Map map, {Encoding? encoding}) { final pairs = >[]; map.forEach( @@ -45,6 +54,7 @@ class Convert { } extension UriX on Uri { + /// Returns a new [Uri] by appending the given [path] to this [Uri]. Uri operator +(String path) { final thisPath = toString(); return Uri.parse(thisPath + path); diff --git a/packages/wyatt_http_client/lib/src/utils/crypto.dart b/packages/wyatt_http_client/lib/src/utils/crypto.dart index f27d08a7..bf7fa5a6 100644 --- a/packages/wyatt_http_client/lib/src/utils/crypto.dart +++ b/packages/wyatt_http_client/lib/src/utils/crypto.dart @@ -18,7 +18,8 @@ import 'dart:convert'; import 'package:crypto/crypto.dart'; -class Crypto { +/// Defines some crypto functions. +abstract class Crypto { /// Hash a string using MD5 static String md5Hash(String data) { final content = const Utf8Encoder().convert(data); diff --git a/packages/wyatt_http_client/lib/src/utils/delay.dart b/packages/wyatt_http_client/lib/src/utils/delay.dart index 3ca50947..cc9d4193 100644 --- a/packages/wyatt_http_client/lib/src/utils/delay.dart +++ b/packages/wyatt_http_client/lib/src/utils/delay.dart @@ -17,7 +17,9 @@ import 'dart:core'; import 'dart:math'; +/// Defines some delay functions. abstract class Delay { + /// Returns a delay based on the [attempt]. static Duration getRetryDelay(int attempt) { assert(attempt >= 0, 'attempt cannot be negative'); if (attempt <= 0) { diff --git a/packages/wyatt_http_client/lib/src/utils/digest_auth.dart b/packages/wyatt_http_client/lib/src/utils/digest_auth.dart index ba16c0aa..065eeef0 100644 --- a/packages/wyatt_http_client/lib/src/utils/digest_auth.dart +++ b/packages/wyatt_http_client/lib/src/utils/digest_auth.dart @@ -19,12 +19,13 @@ import 'dart:math'; import 'package:wyatt_http_client/src/utils/convert.dart'; import 'package:wyatt_http_client/src/utils/crypto.dart'; +/// A class for digest authentication. class DigestAuth { // request counter DigestAuth(this.username, this.password); - String username; - String password; + final String username; + final String password; // must get from first response String? _algorithm; diff --git a/packages/wyatt_http_client/lib/src/utils/header_keys.dart b/packages/wyatt_http_client/lib/src/utils/header_keys.dart index 9fe72ce8..c1c9e986 100644 --- a/packages/wyatt_http_client/lib/src/utils/header_keys.dart +++ b/packages/wyatt_http_client/lib/src/utils/header_keys.dart @@ -14,8 +14,14 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +/// Defines some header keys. abstract class HeaderKeys { + /// The `Authorization` header key. static const String authorization = 'Authorization'; + + /// The `WWW-Authenticate` header key. static const String wwwAuthenticate = 'WWW-Authenticate'; + + /// The `Content-Type` header key. static const String contentType = 'Content-Type'; } diff --git a/packages/wyatt_http_client/lib/src/utils/http_methods.dart b/packages/wyatt_http_client/lib/src/utils/http_methods.dart index 7b0a4c7a..efef38c4 100644 --- a/packages/wyatt_http_client/lib/src/utils/http_methods.dart +++ b/packages/wyatt_http_client/lib/src/utils/http_methods.dart @@ -14,6 +14,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +/// Defines http verb methods. enum HttpMethods { head('HEAD'), get('GET'), @@ -24,5 +25,8 @@ enum HttpMethods { const HttpMethods(this.method); + /// Returns the method of the http verb. + /// + /// For example, the method of [HttpMethods.get] is `GET`. final String method; } diff --git a/packages/wyatt_http_client/lib/src/utils/http_status.dart b/packages/wyatt_http_client/lib/src/utils/http_status.dart index e5360309..f5a5c488 100644 --- a/packages/wyatt_http_client/lib/src/utils/http_status.dart +++ b/packages/wyatt_http_client/lib/src/utils/http_status.dart @@ -83,6 +83,7 @@ enum HttpStatus { const HttpStatus(this.statusCode); + /// Returns the [HttpStatus] with the given [statusCode]. factory HttpStatus.from(int status) => HttpStatus.values.firstWhere((element) => element.statusCode == status); @@ -98,13 +99,18 @@ enum HttpStatus { return false; } + /// Checks if the status code is in the range of 100-199. bool isInfo() => statusCode >= 100 && statusCode < 200; + /// Checks if the status code is in the range of 200-299. bool isSuccess() => statusCode >= 200 && statusCode < 300; + /// Checks if the status code is in the range of 300-399. bool isRedirection() => statusCode >= 300 && statusCode < 400; + /// Checks if the status code is in the range of 400-499. bool isClientError() => statusCode >= 400 && statusCode < 500; + /// Checks if the status code is in the range of 500-599. bool isServerError() => statusCode >= 500 && statusCode < 600; } diff --git a/packages/wyatt_http_client/lib/src/utils/protocols.dart b/packages/wyatt_http_client/lib/src/utils/protocols.dart index 97b9d5f3..bf41a5d6 100644 --- a/packages/wyatt_http_client/lib/src/utils/protocols.dart +++ b/packages/wyatt_http_client/lib/src/utils/protocols.dart @@ -14,9 +14,13 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +/// Defines few protocols enum Protocols { http, https; + /// Returns the scheme of the protocol. + /// + /// For example, the scheme of [Protocols.http] is `http://`. String get scheme => '$name://'; } diff --git a/packages/wyatt_http_client/lib/src/utils/request_utils.dart b/packages/wyatt_http_client/lib/src/utils/request_utils.dart index b50c0063..01794757 100644 --- a/packages/wyatt_http_client/lib/src/utils/request_utils.dart +++ b/packages/wyatt_http_client/lib/src/utils/request_utils.dart @@ -16,6 +16,7 @@ import 'package:http/http.dart'; +/// Defines some request utils. abstract class RequestUtils { static Request _copyNormalRequestWith( Request original, { @@ -38,6 +39,9 @@ abstract class RequestUtils { return request; } + /// Copies the given [original] request and returns a new request with the + /// given [method], [url], [headers], [maxRedirects], [followRedirects], + /// [persistentConnection] and [body]. static BaseRequest copyRequestWith( BaseRequest original, { String? method, @@ -77,6 +81,8 @@ abstract class RequestUtils { return request; } + /// Copies the given [original] request and returns a new request. + /// This method is useful when you want to modify the request static BaseRequest copyRequest(BaseRequest original) { if (original is Request) { return _copyNormalRequest(original); diff --git a/packages/wyatt_http_client/pubspec.yaml b/packages/wyatt_http_client/pubspec.yaml index 699732db..357e00a8 100644 --- a/packages/wyatt_http_client/pubspec.yaml +++ b/packages/wyatt_http_client/pubspec.yaml @@ -3,6 +3,8 @@ description: A Dart client for RESTful APIs with authentication. repository: https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages/src/branch/master/packages/wyatt_http_client version: 1.2.0 +publish_to: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub + environment: sdk: '>=2.17.0 <3.0.0' @@ -12,7 +14,5 @@ dependencies: dev_dependencies: wyatt_analysis: - git: - url: https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages - ref: wyatt_analysis-v2.4.1 - path: packages/wyatt_analysis + hosted: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub + version: ^2.4.1