From 56e86b55bb264ca93ac7a12ee2a629d5203d703a Mon Sep 17 00:00:00 2001 From: Tien Do Nam Date: Tue, 27 Aug 2024 23:15:09 +0200 Subject: [PATCH] feat: add base url setting --- rhttp/CHANGELOG.md | 4 ++ rhttp/README.md | 12 +++++ rhttp/lib/src/model/settings.dart | 7 +++ rhttp/lib/src/request.dart | 9 +++- rhttp/test/integration/base_url_test.dart | 56 +++++++++++++++++++++++ rhttp/test/mocks.dart | 20 +++++++- 6 files changed, 104 insertions(+), 4 deletions(-) create mode 100644 rhttp/test/integration/base_url_test.dart diff --git a/rhttp/CHANGELOG.md b/rhttp/CHANGELOG.md index 504b13c..b2d4abf 100644 --- a/rhttp/CHANGELOG.md +++ b/rhttp/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.6.2 + +- feat: add `baseUrl` setting to `ClientSettings` + ## 0.6.1 - feat: add `onSendProgress` and `onReceiveProgress` diff --git a/rhttp/README.md b/rhttp/README.md index 5b8f4be..d6a2687 100644 --- a/rhttp/README.md +++ b/rhttp/README.md @@ -353,6 +353,18 @@ await Rhttp.get( ); ``` +### ➤ Base URL + +Add a base URL to the client to avoid repeating the same URL or to change the base URL easily. + +```dart +final client = await RhttpClient.create( + settings: const ClientSettings( + baseUrl: 'https://example.com', + ), +); +``` + ### ➤ Interceptors You can add interceptors to the client to modify requests / responses, handle errors, observe requests, etc. diff --git a/rhttp/lib/src/model/settings.dart b/rhttp/lib/src/model/settings.dart index a541e0d..c5b9e71 100644 --- a/rhttp/lib/src/model/settings.dart +++ b/rhttp/lib/src/model/settings.dart @@ -7,11 +7,15 @@ import 'package:rhttp/src/rust/api/http.dart' as rust; export 'package:rhttp/src/rust/api/client.dart' show TlsVersion; +const _keepBaseUrl = '__rhttp_keep__'; const _keepDuration = Duration(microseconds: -9999); const _keepProxySettings = ProxySettings.noProxy(); const _keepTlsSettings = TlsSettings(); class ClientSettings { + /// Base URL to be prefixed to all requests. + final String? baseUrl; + /// The preferred HTTP version to use. final HttpVersionPref httpVersionPref; @@ -32,6 +36,7 @@ class ClientSettings { final TlsSettings? tlsSettings; const ClientSettings({ + this.baseUrl, this.httpVersionPref = HttpVersionPref.all, this.timeout, this.connectTimeout, @@ -41,6 +46,7 @@ class ClientSettings { }); ClientSettings copyWith({ + String? baseUrl = _keepBaseUrl, HttpVersionPref? httpVersionPref, Duration? timeout = _keepDuration, Duration? connectTimeout = _keepDuration, @@ -49,6 +55,7 @@ class ClientSettings { TlsSettings? tlsSettings = _keepTlsSettings, }) { return ClientSettings( + baseUrl: identical(baseUrl, _keepBaseUrl) ? this.baseUrl : baseUrl, httpVersionPref: httpVersionPref ?? this.httpVersionPref, timeout: identical(timeout, _keepDuration) ? this.timeout : timeout, connectTimeout: identical(connectTimeout, _keepDuration) diff --git a/rhttp/lib/src/request.dart b/rhttp/lib/src/request.dart index d9ec168..191bd83 100644 --- a/rhttp/lib/src/request.dart +++ b/rhttp/lib/src/request.dart @@ -122,6 +122,11 @@ Future requestInternalGeneric(HttpRequest request) async { } bool exceptionByInterceptor = false; + final url = switch (request.settings?.baseUrl) { + String baseUrl => baseUrl + request.url, + null => request.url, + }; + try { if (request.expectBody == HttpExpectBody.stream) { final cancelRefCompleter = Completer(); @@ -130,7 +135,7 @@ Future requestInternalGeneric(HttpRequest request) async { clientAddress: request.client?.ref, settings: request.settings?.toRustType(), method: request.method._toRustType(), - url: request.url, + url: url, query: request.query?.entries.map((e) => (e.key, e.value)).toList(), headers: headers?._toRustType(), body: request.body?._toRustType(), @@ -207,7 +212,7 @@ Future requestInternalGeneric(HttpRequest request) async { clientAddress: request.client?.ref, settings: request.settings?.toRustType(), method: request.method._toRustType(), - url: request.url, + url: url, query: request.query?.entries.map((e) => (e.key, e.value)).toList(), headers: headers?._toRustType(), body: request.body?._toRustType(), diff --git a/rhttp/test/integration/base_url_test.dart b/rhttp/test/integration/base_url_test.dart new file mode 100644 index 0000000..6e022aa --- /dev/null +++ b/rhttp/test/integration/base_url_test.dart @@ -0,0 +1,56 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:rhttp/rhttp.dart'; +import 'package:rhttp/src/rust/frb_generated.dart'; + +import '../mocks.dart'; + +void main() { + late MockRustLibApi mockApi; + + setUpAll(() async { + mockApi = MockRustLibApi.createAndRegister(); + + RustLib.initMock(api: mockApi); + }); + + test('Should add base url on client request', () async { + mockApi.mockCreateClient(); + + String? observedUrl; + mockApi.mockDefaultResponse( + onAnswer: (requestUri) { + observedUrl = requestUri; + }, + ); + + final client = await RhttpClient.create( + settings: const ClientSettings( + baseUrl: 'https://mydomain.com/abc', + ), + ); + + await client.get('/def'); + + expect(observedUrl, 'https://mydomain.com/abc/def'); + }); + + test('Should add base url on ad-hoc request', () async { + mockApi.mockCreateClient(); + + String? observedUrl; + mockApi.mockDefaultResponse( + onAnswer: (requestUri) { + observedUrl = requestUri; + }, + ); + + await Rhttp.get( + '/456', + settings: const ClientSettings( + baseUrl: 'https://mydomain.com/123', + ), + ); + + expect(observedUrl, 'https://mydomain.com/123/456'); + }); +} diff --git a/rhttp/test/mocks.dart b/rhttp/test/mocks.dart index 6a2e80e..a12acb7 100644 --- a/rhttp/test/mocks.dart +++ b/rhttp/test/mocks.dart @@ -1,7 +1,7 @@ -import 'dart:typed_data'; - +import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart'; import 'package:mocktail/mocktail.dart'; import 'package:rhttp/src/model/response.dart'; +import 'package:rhttp/src/rust/api/client.dart'; import 'package:rhttp/src/rust/frb_generated.dart'; import 'package:rhttp/src/rust/api/error.dart' as rust_error; import 'package:rhttp/src/rust/api/http.dart' as rust_http; @@ -10,6 +10,10 @@ class MockRustLibApi extends Mock implements RustLibApi { MockRustLibApi.createAndRegister() { registerFallbackValue(rust_http.HttpMethod.get_); registerFallbackValue(rust_http.HttpExpectBody.text); + registerFallbackValue(const ClientSettings( + httpVersionPref: rust_http.HttpVersionPref.http11, + throwOnStatusCode: true, + )); } void mockCustomResponse({ @@ -66,6 +70,8 @@ class MockRustLibApi extends Mock implements RustLibApi { void mockDefaultResponse({void Function(String) onAnswer = _noop}) { when>( () => crateApiHttpMakeHttpRequest( + clientAddress: any(named: 'clientAddress'), + settings: any(named: 'settings'), method: any(named: 'method'), url: any(named: 'url'), expectBody: any(named: 'expectBody'), @@ -108,6 +114,16 @@ class MockRustLibApi extends Mock implements RustLibApi { return onAnswer(invocation.namedArguments[#address]); }); } + + void mockCreateClient() { + when>( + () => crateApiHttpRegisterClient( + settings: any(named: 'settings'), + ), + ).thenAnswer((invocation) async { + return 1; + }); + } } class FakeHttpResponse extends Fake implements HttpTextResponse {