feat(rest-api): adopt contract-first REST API architecture (PoC for #1930) - #1936
Conversation
There was a problem hiding this comment.
Pull request overview
Implements the contract-first REST API layered package architecture (protocol → application → runtime adapter → axum transport) and migrates the v1 torrent detail/list flow (GET /api/v1/torrent/{info_hash}, GET /api/v1/torrents) to use the new application service + adapter instead of direct tracker-core calls.
Changes:
- Added new workspace crates:
rest-api-protocol(v1 DTOs),rest-api-application(ports + use-case),rest-api-runtime-adapter(tracker-backed port impl + conversions). - Refactored
axum-rest-api-servertorrent routes/handlers/resources/tests to consume protocol DTOs and dispatch throughTorrentApiService. - Updated documentation and dependency enforcement artifacts (
docs/packages.md, ADR index + new ADR,deny.toml, workspaceCargo.toml,Containerfile, lockfile).
Reviewed changes
Copilot reviewed 35 out of 36 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/rest-api-runtime-adapter/src/lib.rs | Declares runtime adapter crate modules. |
| packages/rest-api-runtime-adapter/src/conversion.rs | Domain → protocol DTO conversion functions. |
| packages/rest-api-runtime-adapter/src/adapters/mod.rs | Runtime adapter module wiring. |
| packages/rest-api-runtime-adapter/src/adapters/torrent.rs | Implements TorrentQueryPort against tracker in-memory repo. |
| packages/rest-api-runtime-adapter/Cargo.toml | New runtime-adapter crate manifest and deps. |
| packages/rest-api-protocol/src/lib.rs | Declares protocol crate and ownership boundaries. |
| packages/rest-api-protocol/src/v1/mod.rs | Defines v1 contract module structure. |
| packages/rest-api-protocol/src/v1/resources/mod.rs | Exposes v1 resource DTO modules. |
| packages/rest-api-protocol/src/v1/resources/torrent.rs | Adds v1 Torrent/ListItem DTOs. |
| packages/rest-api-protocol/src/v1/resources/peer.rs | Adds v1 Peer/Id DTOs. |
| packages/rest-api-protocol/src/v1/responses.rs | Adds v1 response enum ActionStatus. |
| packages/rest-api-protocol/Cargo.toml | New protocol crate manifest. |
| packages/rest-api-protocol/README.md | Protocol crate README. |
| packages/rest-api-protocol/LICENSE | Protocol crate license file. |
| packages/rest-api-application/src/lib.rs | Declares application layer crate modules. |
| packages/rest-api-application/src/ports/mod.rs | Port trait module container. |
| packages/rest-api-application/src/ports/torrent.rs | Defines TorrentQueryPort boundary. |
| packages/rest-api-application/src/use_cases/mod.rs | Use-case module container. |
| packages/rest-api-application/src/use_cases/torrent.rs | Adds TorrentApiService use-case. |
| packages/rest-api-application/Cargo.toml | New application crate manifest and deps. |
| packages/axum-rest-api-server/src/v1/routes.rs | Wires adapter + use-case into v1 router assembly. |
| packages/axum-rest-api-server/src/v1/context/torrent/routes.rs | Updates torrent routes to use TorrentApiService state. |
| packages/axum-rest-api-server/src/v1/context/torrent/handlers.rs | Refactors handlers to call TorrentApiService. |
| packages/axum-rest-api-server/src/v1/context/torrent/responses.rs | Responses now return protocol DTOs provided by service. |
| packages/axum-rest-api-server/src/v1/context/torrent/resources/torrent.rs | Re-exports protocol torrent DTOs; tests updated to use adapter conversion. |
| packages/axum-rest-api-server/src/v1/context/torrent/resources/peer.rs | Re-exports protocol peer DTOs. |
| packages/axum-rest-api-server/tests/server/v1/contract/context/torrent.rs | Contract test updated to use adapter conversion for peers. |
| packages/axum-rest-api-server/Cargo.toml | Adds dependencies on new REST API layer crates. |
| docs/packages.md | Documents the new REST API contract-first architecture and updates package catalog. |
| docs/issues/open/1930-1669-si-33-rest-api-contract-first-architecture.md | Updates verification/progress checklist. |
| docs/adrs/index.md | Adds ADR entry for contract-first REST API architecture. |
| docs/adrs/20260623200526_adopt_contract-first_architecture_for_rest_api.md | New ADR describing decision and dependency rules. |
| deny.toml | Adds wrapper enforcement for torrust-tracker-rest-api-protocol. |
| Containerfile | Adds build-stub COPY/mkdir entries for new packages. |
| Cargo.toml | Registers new crates as workspace members. |
| Cargo.lock | Records new workspace crates in dependency graph. |
Comments suppressed due to low confidence (1)
docs/issues/open/1930-1669-si-33-rest-api-contract-first-architecture.md:438
- The verification checklist still marks the docs update as incomplete, but this PR adds the REST API architecture section to
docs/packages.mdand introduces an ADR for it. This should be checked to keep the issue spec in sync with the implementation state.
- [x] PoC torrent detail endpoint (`GET /api/v1/torrent/{info_hash}`) migrated through all four target layers:
- `rest-api-protocol`: Torrent/Peer/ListItem DTOs
- `rest-api-application`: `TorrentQueryPort` + `TorrentApiService` use case
- `rest-api-runtime-adapter`: `TrackerTorrentQueryAdapter` + conversion functions
- `axum-rest-api-server`: handler dispatches via use case instead of direct `tracker-core`
- [ ] Target architecture documented in `docs/packages.md` (or a dedicated ADR).
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Review comments addressedAll Copilot review feedback has been fixed in the latest commits:
|
…ckage Introduce torrust-tracker-rest-api-protocol as the dedicated contract package for REST API wire-format DTOs. This is the first step toward the contract-first architecture defined in issue torrust#1930. The package initially contains: - v1 Torrent, Peer, and ListItem DTOs - v1 ActionStatus response type - README and AGPL-3.0 LICENSE - Workspace member registration Also update Containerfile to include the new package's stub sources for containerized builds. Part of EPIC torrust#1669
Replace locally-defined Torrent, Peer, Id, and ListItem DTOs with re-exports from torrust-tracker-rest-api-protocol. This freezes the v1 wire contract as a shared protocol artifact. Key changes: - Add torrust-tracker-rest-api-protocol dependency - Replace From<domain> impls with free conversion functions (orphan rule prevents implementing From for foreign types) - Update responses.rs to reference protocol types - Update integration test to use from_domain_peer() Part of EPIC torrust#1669 — PoC step for issue torrust#1930
Introduce the application and runtime-adapter layers for the REST API contract-first architecture (PoC for torrust#1930). New packages: - rest-api-application: TorrentQueryPort trait and TorrentApiService use case - rest-api-runtime-adapter: TrackerTorrentQueryAdapter + conversion functions Wiring changes: - axum-rest-api-server torrent handler now uses TorrentApiService instead of directly calling tracker-core services - Route wiring composes adapter -> service -> handler in v1/routes.rs - Old local conversion functions replaced by runtime adapter module - Integration test updated to use conversion::from_domain_peer Dependency rules satisfied: - axum-rest-api-server -> rest-api-application + rest-api-protocol - rest-api-runtime-adapter -> rest-api-application + tracker-core - rest-api-application -> rest-api-protocol only (no tracker deps) Also updates Containerfile and workspace members for both new packages. Part of EPIC torrust#1669
Add ADR and update docs/packages.md to reflect the new package layers introduced in issue torrust#1930. ADR covers: - Four-layer architecture (protocol, application, runtime adapter, transport) - Dependency rules and forbidden edges - Alternatives considered (mirror UDP/HTTP layering, jump to v2) - Long-term vision for a tracker-agnostic API standard docs/packages.md updates: - Directory tree with new packages - Package conventions for rest-api-* prefix - REST API architecture section with Mermaid diagram and dependency table - Package catalog entries for all four layers - REST API-specific forbidden edges table - deny.toml entry for rest-api-protocol ban enforcement Part of EPIC torrust#1669
…Os directly Remove pub-use re-exports from axum-rest-api-server's peer.rs and torrent.rs that forwarded to torrust-tracker-rest-api-protocol. All callers now import directly from the protocol crate. Updated files: - packages/axum-rest-api-server/tests/ (asserts, contract test) - src/console/ci/qbittorrent_e2e/ (verify_tracker_swarm, tracker client) - packages/axum-rest-api-server/src/v1/context/torrent/resources/ (tests) Also adds torrust-tracker-rest-api-protocol as a direct dependency of the root torrust-tracker crate. Part of EPIC torrust#1669
Restructure the Mermaid diagram so outer layers (transport, client) appear at the top and inner layers (runtime adapter, tracker internals) at the bottom, matching the server -> core -> protocol convention. Solid arrows: direct calls/implementation Dashed arrows: serialization/deserialization of shared contract Part of EPIC torrust#1669
- Fix broken ADR link in docs/packages.md (remove docs/ prefix) - Add missing README.md + LICENSE for rest-api-application and rest-api-runtime-adapter packages - Clarify peer.updated vs peer.updated_milliseconds_ago in DTO docs - Mark architecture documentation checkbox complete in issue spec Part of EPIC torrust#1669
…up tasks - New draft issue at docs/issues/drafts/ with full analysis of the misleading 'updated_milliseconds_ago' field name, based on git archaeology (commit bc3d246, Nov 2022). - Set as subissue of EPIC torrust#144 (API v2). - Approach: additive — add 'updated_at_ms' alongside existing fields, remove old fields in v2. - Update issue torrust#1930 spec follow-up tasks section with the final name. Part of EPIC torrust#1669
e2419e1 to
336b342
Compare
…migration (SI-33 follow-up) - EPIC torrust#1938 tracks progressive migration of remaining API contexts - SI-1 (torrust#1939): migrate health_check context - SI-2 (torrust#1940): migrate whitelist context - SI-3 (torrust#1941): migrate auth_key context - SI-4 (torrust#1942): migrate stats context - SI-5 (torrust#1943): deprecate rest-api-core - SI-6 (torrust#1944): introduce ApiClient high-level client
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 50 out of 51 changed files in this pull request and generated 3 comments.
Comments suppressed due to low confidence (1)
packages/axum-rest-api-server/src/v1/context/torrent/resources/peer.rs:4
resources::peeris now an empty module, but older references/imports may still usecrate::v1::context::torrent::resources::peer::Peer(and it keeps the module around). Re-exporting the protocol DTOs here maintains a stable public path without reintroducing DTO ownership in the Axum crate.
//! `Peer` and Peer `Id` API resources.
//!
//! Protocol DTOs are defined in `torrust-tracker-rest-api-protocol`.
|
ACK bfcafb3 |
Summary
This PR implements a contract-first package architecture for the REST API, as specified in issue #1930 (part of EPIC #1669 — Overhaul: Packages). The PoC validates the architecture with the torrent detail endpoint (
GET /api/v1/torrent/{info_hash}) migrated through all four target layers.Architecture
flowchart LR P[rest-api-protocol] A[rest-api-application] R[rest-api-runtime-adapter] T[axum-rest-api-server] I[tracker-core / udp-core / http-core] T --> A T --> P A --> P R --> A R --> Irest-api-protocolrest-api-applicationTorrentQueryPort), use-case services (TorrentApiService). Depends only on protocol.rest-api-runtime-adapterTrackerTorrentQueryAdapter, domain→DTO conversion functions. Only layer that depends on tracker internals.axum-rest-api-serverWhat's implemented
rest-api-protocol— contract package with v1 Torrent, Peer, ListItem, ActionStatus DTOsrest-api-application—TorrentQueryPorttrait +TorrentApiServiceuse caserest-api-runtime-adapter—TrackerTorrentQueryAdapter+ domain→protocol conversionstracker-corecallsdocs/packages.mdupdated with REST API architecture section, Mermaid diagram, and dependency tablesdeny.tomlupdated withrest-api-protocolban enforcementFollow-up EPIC #1938
The remaining REST API contexts (health_check, whitelist, auth_key, stats) and client improvements are tracked in the new dedicated EPIC:
health_checkcontextwhitelistcontextauth_keycontextstatscontextrest-api-coreApiClienthigh-level clientDependency rules satisfied
axum-rest-api-server → rest-api-applicationaxum-rest-api-server → rest-api-protocolrest-api-application → rest-api-protocolrest-api-runtime-adapter → rest-api-application + tracker-coreaxum-rest-api-server → tracker-core(direct)The forbidden edge
axum-rest-api-server → tracker-corestill exists for non-torrent contexts (whitelist, auth keys, stats) and is tracked for follow-up migration in EPIC #1938.Why is
axum-rest-api-server → tracker-coreforbidden?This is a common point of confusion — the direction is structurally allowed (higher-level package depending on a lower-level one). The edge is forbidden anyway because of separation of concerns:
Prevents domain types from leaking into the API contract. When the Axum handler imports
tracker_core::whitelist::WhitelistManagerdirectly, changes totracker-coreinternals ripple into the wire format. The protocol package must be the sole source of truth for API types.Enables testability without the tracker stack. An Axum handler taking
State<Arc<WhitelistManager>>needs real tracker infrastructure to test. The same handler takingState<Arc<WhitelistApiService>>can be tested against a mock adapter.Keeps the Axum server a thin transport adapter. Its job: extract HTTP request → call a use-case → serialize to HTTP response. Not: construct
KeysHandler, callWhitelistManager::add_torrent_to_whitelist, or mapPeerKeyErrorvariants.Enables a tracker-agnostic API. If
axum-rest-api-serverdepends ontracker-core, the REST API is permanently tied to Torrust's tracker. With the contract-first architecture, the same protocol and application layers could serve as the REST API for any BitTorrent tracker implementing the port traits.The correct lower-level dependency for
axum-rest-api-serverisrest-api-application(port traits and use-cases). The bridge totracker-coreisrest-api-runtime-adapter— the only layer that imports tracker-internal crates directly.Design notes
From<domain_type> for protocol_typewhen the protocol type comes from a different crate. Free conversion functions inrest-api-runtime-adapter::conversionare the current approach.Documentation
Part of EPIC #1669