For full project context see the root AGENTS.md.
Defines and loads all tracker configuration. Version 3.0.0 structs live under
src/v3_0_0/. Version 2.0.0 structs live under src/v2_0_0/ and are kept for
backward compatibility.
This is the most common mistake to avoid in this package.
When adding a configuration field that has a domain constraint — a rule that makes the valid value space smaller than the raw primitive — you must use a typed newtype, not a raw primitive.
Wrong:
// ✗ Option<String> carries no invariant — consuming code must re-validate.
pub public_url: Option<String>,
// ✗ url::Url is parsed but the scheme is not constrained.
pub public_url: Option<url::Url>,Correct:
// ✓ HttpUrl guarantees http:// or https:// at the type level.
pub public_url: Option<HttpUrl>,
// ✓ UdpUrl guarantees udp:// at the type level.
pub public_url: Option<UdpUrl>,Implementation checklist when adding a new constrained field type:
- Define the newtype in the appropriate module (scheme-constrained URL types live
in
src/v3_0_0/public_url.rs). - Implement
new(inner) -> Result<Self, String>— validate the constraint. - Implement
parse(s: &str) -> Result<Self, String>— parse then validate. - Implement
Serialize— delegate to the inner value's string form. - Implement
Deserialize— callSelf::parseand map errors tode::Error::custom. - Implement
Display,AsRef<str>(andAsRef<InnerType>if useful) for ergonomic access in consuming code. - Write tests: accept valid value, reject invalid value, round-trip through TOML.
- Use
#[serde(default)]on the struct field — nodeserialize_withattribute is needed because the type'sDeserializeimpl handles validation.
Granularity rule: Use the narrowest type that captures the actual constraint.
Do not create a service-specific subtype (e.g. HttpTrackerUrl) unless the
service protocol imposes a constraint on the URL itself beyond the scheme
(e.g. a mandatory path required by a BitTorrent Enhancement Proposal).
Full rationale: ADR 20260721100000
Every v3_0_0 configuration struct must carry #[serde(deny_unknown_fields)].
This rejects typos and stale keys at deserialization time instead of silently
ignoring them.
Each struct field that has a non-obvious default must be wired through a private
associated function used as the #[serde(default = "...")] target:
#[serde(default = "HttpTracker::default_bind_address")]
pub bind_address: SocketAddr,
fn default_bind_address() -> SocketAddr { ... }This makes the default value explicit and independently testable.