Skip to content

Commit 078dce7

Browse files
committed
refactor(rest-api-client): align client with rest-api-protocol and add typed ApiClient
1 parent 082142b commit 078dce7

12 files changed

Lines changed: 309 additions & 108 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/issues/open/1944-1938-si-6-align-rest-api-client.md

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,16 @@ semantic-links:
2424

2525
## Subissue of REST API Contract-First Migration EPIC
2626

27+
## Clarifying Decisions (from AI agent Q&A with user)
28+
29+
- **`AddKeyForm`**: Use the protocol package's `AddKeyForm` (with field `opt_seconds_valid`) and remove the local `AddKeyForm` from client.
30+
- **`ClientError` enum variants**:
31+
- `TransportError(reqwest::Error)` — network/connection failures
32+
- `ApiError { status: StatusCode, body: String }` — non-2xx responses with the error body
33+
- `DeserializationError(reqwest::Error)` — JSON parsing failures
34+
- **Public `get()` function**: Keep as a public free function (used by health_check tests directly).
35+
- **Re-export strategy**: Re-export both `ApiClient` and `ApiHttpClient` from the crate root for ergonomics.
36+
2737
## Problem
2838

2939
The REST API client package (`torrust-tracker-rest-api-client`) currently exposes only a **low-level** `Client` struct where all 10 methods return raw `reqwest::Response` values. Callers must manually deserialize responses and handle errors. Some internal methods use `.unwrap()`, panicking on transport errors.
@@ -175,21 +185,21 @@ impl ApiClient {
175185

176186
| ID | Status | Task | Notes |
177187
| --- | ------ | ------------------------------------------------------------ | ------------------------------------------------ |
178-
| T1 | TODO | Rename `Client``ApiHttpClient` in `client.rs` | Compiler catches all references |
179-
| T2 | TODO | Add `rest-api-protocol` to `rest-api-client/Cargo.toml` | |
180-
| T3 | TODO | Define `ClientError` enum | Wraps reqwest error, deserialization, API errors |
181-
| T4 | TODO | Add `ApiClient` struct before `ApiHttpClient` in `client.rs` | New high-level typed client |
182-
| T5 | TODO | Implement typed methods on `ApiClient` for all endpoints | Returns `Result<DtoType, ClientError>` |
188+
| T1 | DONE | Rename `Client``ApiHttpClient` in `client.rs` | Compiler catches all references |
189+
| T2 | DONE | Add `rest-api-protocol` to `rest-api-client/Cargo.toml` | |
190+
| T3 | DONE | Define `ClientError` enum | Wraps reqwest error, deserialization, API errors |
191+
| T4 | DONE | Add `ApiClient` struct before `ApiHttpClient` in `client.rs` | New high-level typed client |
192+
| T5 | DONE | Implement typed methods on `ApiClient` for all endpoints | Returns `Result<DtoType, ClientError>` |
183193
| T6 | TODO | Verify pre-commit and pre-push checks pass | |
184194

185195
## Verification / Progress
186196

187-
- [ ] `Client` renamed to `ApiHttpClient` across the codebase
188-
- [ ] `rest-api-protocol` added as dependency
189-
- [ ] `ClientError` enum defined
190-
- [ ] `ApiClient` struct with typed methods for all endpoints added
191-
- [ ] `ApiClient` appears before `ApiHttpClient` in `client.rs`
192-
- [ ] All existing tests pass unchanged
197+
- [x] `Client` renamed to `ApiHttpClient` across the codebase
198+
- [x] `rest-api-protocol` added as dependency
199+
- [x] `ClientError` enum defined
200+
- [x] `ApiClient` struct with typed methods for all endpoints added
201+
- [x] `ApiClient` appears before `ApiHttpClient` in `client.rs`
202+
- [x] All existing tests pass unchanged
193203
- [ ] Pre-commit checks pass
194204
- [ ] Pre-push checks pass
195205

packages/axum-rest-api-server/tests/server/v1/contract/authentication.rs

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ mod given_that_the_token_is_only_provided_in_the_authentication_header {
44
use torrust_tracker_rest_api_client::common::http::Query;
55
use torrust_tracker_rest_api_client::connection_info::ConnectionInfo;
66
use torrust_tracker_rest_api_client::v1::client::{
7-
AUTH_BEARER_TOKEN_HEADER_PREFIX, Client, headers_with_auth_token, headers_with_request_id,
7+
AUTH_BEARER_TOKEN_HEADER_PREFIX, ApiHttpClient, headers_with_auth_token, headers_with_request_id,
88
};
99
use torrust_tracker_test_helpers::logging::logs_contains_a_line_with;
1010
use torrust_tracker_test_helpers::{configuration, logging};
@@ -20,7 +20,7 @@ mod given_that_the_token_is_only_provided_in_the_authentication_header {
2020

2121
let token = env.get_connection_info().api_token.unwrap();
2222

23-
let response = Client::new(env.get_connection_info())
23+
let response = ApiHttpClient::new(env.get_connection_info())
2424
.unwrap()
2525
.get_request_with_query("stats", Query::default(), Some(headers_with_auth_token(&token)))
2626
.await;
@@ -48,7 +48,7 @@ mod given_that_the_token_is_only_provided_in_the_authentication_header {
4848
.expect("the auth token is not a valid header value"),
4949
);
5050

51-
let response = Client::new(env.get_connection_info())
51+
let response = ApiHttpClient::new(env.get_connection_info())
5252
.unwrap()
5353
.get_request_with_query("stats", Query::default(), Some(headers))
5454
.await;
@@ -83,7 +83,7 @@ mod given_that_the_token_is_only_provided_in_the_authentication_header {
8383

8484
let connection_info = ConnectionInfo::anonymous(env.get_connection_info().origin);
8585

86-
let response = Client::new(connection_info)
86+
let response = ApiHttpClient::new(connection_info)
8787
.unwrap()
8888
.get_request_with_query("stats", Query::default(), Some(headers))
8989
.await;
@@ -103,7 +103,7 @@ mod given_that_the_token_is_only_provided_in_the_query_param {
103103
use torrust_tracker_axum_rest_api_server::testing::environment::Started;
104104
use torrust_tracker_rest_api_client::common::http::{Query, QueryParam};
105105
use torrust_tracker_rest_api_client::connection_info::ConnectionInfo;
106-
use torrust_tracker_rest_api_client::v1::client::{Client, TOKEN_PARAM_NAME, headers_with_request_id};
106+
use torrust_tracker_rest_api_client::v1::client::{ApiHttpClient, TOKEN_PARAM_NAME, headers_with_request_id};
107107
use torrust_tracker_test_helpers::logging::logs_contains_a_line_with;
108108
use torrust_tracker_test_helpers::{configuration, logging};
109109
use uuid::Uuid;
@@ -120,7 +120,7 @@ mod given_that_the_token_is_only_provided_in_the_query_param {
120120

121121
let connection_info = ConnectionInfo::anonymous(env.get_connection_info().origin);
122122

123-
let response = Client::new(connection_info)
123+
let response = ApiHttpClient::new(connection_info)
124124
.unwrap()
125125
.get_request_with_query(
126126
"stats",
@@ -144,7 +144,7 @@ mod given_that_the_token_is_only_provided_in_the_query_param {
144144

145145
let connection_info = ConnectionInfo::anonymous(env.get_connection_info().origin);
146146

147-
let response = Client::new(connection_info)
147+
let response = ApiHttpClient::new(connection_info)
148148
.unwrap()
149149
.get_request_with_query(
150150
"stats",
@@ -173,7 +173,7 @@ mod given_that_the_token_is_only_provided_in_the_query_param {
173173

174174
let connection_info = ConnectionInfo::anonymous(env.get_connection_info().origin);
175175

176-
let response = Client::new(connection_info)
176+
let response = ApiHttpClient::new(connection_info)
177177
.unwrap()
178178
.get_request_with_query(
179179
"stats",
@@ -203,15 +203,15 @@ mod given_that_the_token_is_only_provided_in_the_query_param {
203203
let connection_info = ConnectionInfo::anonymous(env.get_connection_info().origin);
204204

205205
// At the beginning of the query component
206-
let response = Client::new(connection_info)
206+
let response = ApiHttpClient::new(connection_info)
207207
.unwrap()
208208
.get_request(&format!("torrents?token={token}&limit=1"))
209209
.await;
210210

211211
assert_eq!(response.status(), 200);
212212

213213
// At the end of the query component
214-
let response = Client::new(env.get_connection_info())
214+
let response = ApiHttpClient::new(env.get_connection_info())
215215
.unwrap()
216216
.get_request(&format!("torrents?limit=1&token={token}"))
217217
.await;
@@ -227,7 +227,7 @@ mod given_that_not_token_is_provided {
227227
use torrust_tracker_axum_rest_api_server::testing::environment::Started;
228228
use torrust_tracker_rest_api_client::common::http::Query;
229229
use torrust_tracker_rest_api_client::connection_info::ConnectionInfo;
230-
use torrust_tracker_rest_api_client::v1::client::{Client, headers_with_request_id};
230+
use torrust_tracker_rest_api_client::v1::client::{ApiHttpClient, headers_with_request_id};
231231
use torrust_tracker_test_helpers::logging::logs_contains_a_line_with;
232232
use torrust_tracker_test_helpers::{configuration, logging};
233233
use uuid::Uuid;
@@ -244,7 +244,7 @@ mod given_that_not_token_is_provided {
244244

245245
let connection_info = ConnectionInfo::anonymous(env.get_connection_info().origin);
246246

247-
let response = Client::new(connection_info)
247+
let response = ApiHttpClient::new(connection_info)
248248
.unwrap()
249249
.get_request_with_query("stats", Query::default(), Some(headers_with_request_id(request_id)))
250250
.await;
@@ -263,7 +263,7 @@ mod given_that_not_token_is_provided {
263263
mod given_that_token_is_provided_via_get_param_and_authentication_header {
264264
use torrust_tracker_axum_rest_api_server::testing::environment::Started;
265265
use torrust_tracker_rest_api_client::common::http::{Query, QueryParam};
266-
use torrust_tracker_rest_api_client::v1::client::{Client, TOKEN_PARAM_NAME, headers_with_auth_token};
266+
use torrust_tracker_rest_api_client::v1::client::{ApiHttpClient, TOKEN_PARAM_NAME, headers_with_auth_token};
267267
use torrust_tracker_test_helpers::{configuration, logging};
268268

269269
#[tokio::test]
@@ -276,7 +276,7 @@ mod given_that_token_is_provided_via_get_param_and_authentication_header {
276276

277277
let non_authorized_token = "NonAuthorizedToken";
278278

279-
let response = Client::new(env.get_connection_info())
279+
let response = ApiHttpClient::new(env.get_connection_info())
280280
.unwrap()
281281
.get_request_with_query(
282282
"stats",

0 commit comments

Comments
 (0)