Skip to content

feat(rest-api): adopt contract-first REST API architecture (PoC for #1930) - #1936

Merged
josecelano merged 11 commits into
torrust:developfrom
josecelano:1930-rest-api-contract-first-poc
Jun 24, 2026
Merged

feat(rest-api): adopt contract-first REST API architecture (PoC for #1930)#1936
josecelano merged 11 commits into
torrust:developfrom
josecelano:1930-rest-api-contract-first-poc

Conversation

@josecelano

@josecelano josecelano commented Jun 23, 2026

Copy link
Copy Markdown
Member

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 --> I
Loading
Layer Package Responsibility
Protocol rest-api-protocol Versioned DTOs, error schemas, auth semantics. No Axum, no tracker internals.
Application rest-api-application Port traits (TorrentQueryPort), use-case services (TorrentApiService). Depends only on protocol.
Runtime adapter rest-api-runtime-adapter TrackerTorrentQueryAdapter, domain→DTO conversion functions. Only layer that depends on tracker internals.
Transport axum-rest-api-server HTTP routing, extraction, response serialization. Thin — no business logic.

What's implemented

  • rest-api-protocol — contract package with v1 Torrent, Peer, ListItem, ActionStatus DTOs
  • rest-api-applicationTorrentQueryPort trait + TorrentApiService use case
  • rest-api-runtime-adapterTrackerTorrentQueryAdapter + domain→protocol conversions
  • Handler wiring — torrent endpoint dispatches via use case instead of direct tracker-core calls
  • Integration test updated to use adapter conversion functions
  • ADR documenting the decision, alternatives considered, and long-term vision
  • docs/packages.md updated with REST API architecture section, Mermaid diagram, and dependency tables
  • deny.toml updated with rest-api-protocol ban enforcement
  • Containerfile updated with stub sources for all new packages

Follow-up EPIC #1938

The remaining REST API contexts (health_check, whitelist, auth_key, stats) and client improvements are tracked in the new dedicated EPIC:

Dependency rules satisfied

Edge Status
axum-rest-api-server → rest-api-application
axum-rest-api-server → rest-api-protocol
rest-api-application → rest-api-protocol
rest-api-runtime-adapter → rest-api-application + tracker-core
axum-rest-api-server → tracker-core (direct) ❌ forbidden (in progress, tracked in EPIC #1938)

The forbidden edge axum-rest-api-server → tracker-core still 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-core forbidden?

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:

  1. Prevents domain types from leaking into the API contract. When the Axum handler imports tracker_core::whitelist::WhitelistManager directly, changes to tracker-core internals ripple into the wire format. The protocol package must be the sole source of truth for API types.

  2. Enables testability without the tracker stack. An Axum handler taking State<Arc<WhitelistManager>> needs real tracker infrastructure to test. The same handler taking State<Arc<WhitelistApiService>> can be tested against a mock adapter.

  3. Keeps the Axum server a thin transport adapter. Its job: extract HTTP request → call a use-case → serialize to HTTP response. Not: construct KeysHandler, call WhitelistManager::add_torrent_to_whitelist, or map PeerKeyError variants.

  4. Enables a tracker-agnostic API. If axum-rest-api-server depends on tracker-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-server is rest-api-application (port traits and use-cases). The bridge to tracker-core is rest-api-runtime-adapter — the only layer that imports tracker-internal crates directly.

Design notes

  • The orphan rule prevents From<domain_type> for protocol_type when the protocol type comes from a different crate. Free conversion functions in rest-api-runtime-adapter::conversion are the current approach.
  • This is a PoC — future work should migrate remaining contexts (whitelist, auth keys, stats) to the same layered pattern.
  • The protocol contract package is positioned for potential extraction into a standalone, tracker-agnostic REST API standard.

Documentation

Part of EPIC #1669

@josecelano josecelano self-assigned this Jun 23, 2026
@josecelano josecelano changed the title feat: contract-first REST API protocol package (PoC for #1930) feat(rest-api): adopt contract-first REST API architecture (PoC for #1930) Jun 23, 2026
@josecelano
josecelano marked this pull request as ready for review June 23, 2026 20:56
Copilot AI review requested due to automatic review settings June 23, 2026 20:56
@josecelano josecelano changed the title feat(rest-api): adopt contract-first REST API architecture (PoC for #1930) feat(rest-api): adopt contract-first REST API architecture Jun 23, 2026

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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-server torrent routes/handlers/resources/tests to consume protocol DTOs and dispatch through TorrentApiService.
  • Updated documentation and dependency enforcement artifacts (docs/packages.md, ADR index + new ADR, deny.toml, workspace Cargo.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.md and 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.

Comment thread docs/packages.md
Comment thread packages/rest-api-application/Cargo.toml
Comment thread packages/rest-api-runtime-adapter/Cargo.toml
Comment thread packages/rest-api-protocol/src/v1/resources/peer.rs Outdated
@josecelano

Copy link
Copy Markdown
Member Author

Review comments addressed

All Copilot review feedback has been fixed in the latest commits:

  1. Broken ADR link (docs/packages.md:63) — ✅ Fixed. Changed docs/adrs/... to adrs/... (relative from docs/).

  2. Missing README.md (rest-api-application, rest-api-runtime-adapter) — ✅ Fixed. Added README.md + LICENSE to both new packages.

  3. updated_milliseconds_ago field docs — ✅ Fixed. Updated doc comment to: "Milliseconds since the peer's last update (relative to the response generation time)" to clarify it's a relative duration, not a timestamp. Also clarified updated as "Unix timestamp in milliseconds since epoch" to highlight the difference.

  4. Issue spec checkbox (docs/issues/open/1930-1669-si-33-rest-api-contract-first-architecture.md:438) — ✅ Fixed. Marked architecture documentation box as completed with reference to ADR 20260623200526 + packages.md REST API section.

@josecelano josecelano changed the title feat(rest-api): adopt contract-first REST API architecture feat(rest-api): adopt contract-first REST API architecture (PoC for #1930) Jun 24, 2026
…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
@josecelano
josecelano force-pushed the 1930-rest-api-contract-first-poc branch from e2419e1 to 336b342 Compare June 24, 2026 10:01
…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

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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::peer is now an empty module, but older references/imports may still use crate::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`.

Comment thread packages/rest-api-protocol/src/v1/resources/peer.rs
Comment thread packages/rest-api-protocol/src/v1/resources/peer.rs Outdated
@josecelano

Copy link
Copy Markdown
Member Author

ACK bfcafb3

@josecelano
josecelano merged commit c0aa941 into torrust:develop Jun 24, 2026
16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

SI-33: Define REST API contract-first package architecture for EPIC #1669

2 participants