diff --git a/CHANGELOG.md b/CHANGELOG.md index 4867903..c30c15b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 `fluent_uri::Uri` instead of a `url::Url` previously - all error types with an `InvalidURL` variant now have `fluent_uri::ParseError` as source instead of `url::ParseError` previously +- `TrackerScheme` variant `UDP` has been renamed `Udp` to be more consistent with + other variants, and other rust types +- `TrackerScheme` no longer derives de/serialize because that's not actually + used in torrent files + ### Added @@ -25,6 +30,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added new `MagnetLinkError` variants to be more precise about what's wrong with a parsed magnet link. - `MagnetLink::trackers` lists the trackers in the magnet link +- `TrackerScheme` and `Tracker` implement `FromStr` + +### Fixed + +- `Tracker` (de)serialization implementation is now custom and only uses the + inner URL instead of trying to (de)serialize all fields which was wrong ## Version 0.3.2 (2025-08-29) diff --git a/Cargo.toml b/Cargo.toml index f25abff..024a324 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,7 @@ sha1 = "0.10" sha256 = "1.5" rustc-hex = "2.1" serde = { version = "1", features = [ "derive" ] } -fluent-uri = "0.4" +fluent-uri = { version = "0.4", features = [ "serde" ] } [dev-dependencies] serde_json = "1" diff --git a/src/tracker.rs b/src/tracker.rs index a780d1c..2838420 100644 --- a/src/tracker.rs +++ b/src/tracker.rs @@ -1,4 +1,7 @@ use fluent_uri::{ParseError as UriParseError, Uri}; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + +use std::str::FromStr; /// A source of peers. Can be a [`Tracker`](crate::tracker::Tracker) or a decentralized source. #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] @@ -10,27 +13,32 @@ pub enum PeerSource { } /// A centralized variant of a [`Peersource`](crate::tracker::PeerSource). -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +#[derive(Clone, Debug, PartialEq)] pub struct Tracker { scheme: TrackerScheme, url: Uri, } impl Tracker { - pub fn new_http(uri: &str) -> Result { - let uri = Uri::parse(uri)?; - Ok(Self { - scheme: TrackerScheme::Http, - url: uri.into(), - }) + /// Generate a new Tracker from a given string URL. + pub fn new(url: &str) -> Result { + let url = Uri::parse(url.to_string())?; + Tracker::from_url(&url) } - pub fn new_udp(_uri: &str) -> Result { - unimplemented!(); + /// Generate a new Tracker from a parsed URL. + /// + /// Will fail if scheme is not "http", "https", "wss" or "udp". + pub fn from_url(url: &Uri) -> Result { + Ok(Tracker { + scheme: TrackerScheme::from_str(url.scheme().as_str())?, + url: url.clone(), + }) } - pub fn new_ws(_uri: &str) -> Result { - unimplemented!(); + /// Turns a centralized Tracker into a wider PeerSource + pub fn to_peer_source(&self) -> PeerSource { + PeerSource::from_tracker(self) } pub fn scheme(&self) -> &TrackerScheme { @@ -42,12 +50,59 @@ impl Tracker { } } +impl<'de> Deserialize<'de> for Tracker { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + Tracker::new(&s).map_err(serde::de::Error::custom) + } +} + +impl Serialize for Tracker { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + // serializer.serialize(&self.url) + self.url.serialize(serializer) + } +} + +impl FromStr for Tracker { + type Err = TrackerError; + + fn from_str(s: &str) -> Result { + Self::new(s) + } +} + /// A protocol used by a [`Tracker`](crate::tracker::Tracker). -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +/// +/// Does not implement Serialize/Deserialize because it's actually not in the +/// torrent data. It is constructed from the parsed tracker URLs contained in +/// the torrent data. +#[derive(Clone, Debug, PartialEq)] pub enum TrackerScheme { Websocket, Http, - UDP, + Udp, +} + +impl FromStr for TrackerScheme { + type Err = TrackerError; + + fn from_str(s: &str) -> Result { + match s { + "http" | "https" => Ok(Self::Http), + "ws" => Ok(Self::Websocket), + "udp" => Ok(Self::Udp), + _ => Err(TrackerError::InvalidScheme { + scheme: s.to_string(), + }), + } + } } /// Error occurred during parsing a [`Tracker`](crate::tracker::Tracker). @@ -103,40 +158,6 @@ impl PeerSource { } } -impl Tracker { - /// Generate a new Tracker from a given string URL. - pub fn new(url: &str) -> Result { - let url = Uri::parse(url.to_string())?; - Tracker::from_url(&url) - } - - /// Generate a new Tracker from a parsed URL. - /// - /// Will fail if scheme is not "http", "https", "wss" or "udp". - pub fn from_url(url: &Uri) -> Result { - let scheme = match url.scheme().as_str() { - "http" | "https" => TrackerScheme::Http, - "wss" => TrackerScheme::Websocket, - "udp" => TrackerScheme::UDP, - _ => { - return Err(TrackerError::InvalidScheme { - scheme: url.scheme().to_string(), - }); - } - }; - - Ok(Tracker { - scheme, - url: url.clone(), - }) - } - - /// Turns a centralized Tracker into a wider PeerSource - pub fn to_peer_source(&self) -> PeerSource { - PeerSource::from_tracker(self) - } -} - /// Turn a backend-specific tracker struct into an agnostic [`Tracker`](crate::tracker::Tracker). pub trait TryIntoTracker { fn try_into_tracker(&self) -> Result;