use serde::{Deserialize, Serialize};
use std::str::FromStr;
use crate::{InfoHash, InfoHashError, TorrentID};
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(try_from = "&str")]
pub struct SingleTarget(String);
impl SingleTarget {
pub fn new(hash: &str) -> Result<SingleTarget, InfoHashError> {
let hash = InfoHash::new(hash)?;
Ok(SingleTarget(hash.to_string()))
}
pub fn as_str(&self) -> &str {
&self.0
}
pub fn truncated(&self) -> &str {
self.as_str().get(0..40).unwrap()
}
pub fn matches_hash(&self, hash: &InfoHash) -> bool {
match hash {
InfoHash::V1(h) => h.as_str() == self.as_str(),
InfoHash::Hybrid((v1, _v2)) => {
hash.id().as_str() == self.truncated() || v1 == self.as_str()
}
InfoHash::V2(h) => {
h.as_str() == self.as_str() || hash.id().as_str() == self.as_str()
}
}
}
}
impl std::fmt::Display for SingleTarget {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl TryFrom<&str> for SingleTarget {
type Error = InfoHashError;
fn try_from(value: &str) -> Result<Self, Self::Error> {
Self::from_str(value)
}
}
impl FromStr for SingleTarget {
type Err = InfoHashError;
fn from_str(value: &str) -> Result<SingleTarget, InfoHashError> {
SingleTarget::new(value)
}
}
pub trait ToSingleTarget {
fn to_single_target(&self) -> Result<SingleTarget, InfoHashError>;
}
impl ToSingleTarget for &str {
fn to_single_target(&self) -> Result<SingleTarget, InfoHashError> {
SingleTarget::new(self)
}
}
impl ToSingleTarget for SingleTarget {
fn to_single_target(&self) -> Result<SingleTarget, InfoHashError> {
Ok(self.clone())
}
}
impl From<InfoHash> for SingleTarget {
fn from(value: InfoHash) -> SingleTarget {
SingleTarget::new(value.as_str()).unwrap()
}
}
impl From<&InfoHash> for SingleTarget {
fn from(value: &InfoHash) -> SingleTarget {
SingleTarget::new(value.as_str()).unwrap()
}
}
impl From<TorrentID> for SingleTarget {
fn from(value: TorrentID) -> SingleTarget {
SingleTarget::new(value.as_str()).unwrap()
}
}
impl From<&TorrentID> for SingleTarget {
fn from(value: &TorrentID) -> SingleTarget {
SingleTarget::new(value.as_str()).unwrap()
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum MultiTarget {
All,
Hash(SingleTarget),
}
impl FromStr for MultiTarget {
type Err = InfoHashError;
#[allow(dead_code)]
fn from_str(value: &str) -> Result<MultiTarget, Self::Err> {
if value == "all" {
Ok(MultiTarget::All)
} else {
Ok(MultiTarget::Hash(SingleTarget::new(value)?))
}
}
}
impl TryFrom<&str> for MultiTarget {
type Error = InfoHashError;
fn try_from(value: &str) -> Result<MultiTarget, Self::Error> {
MultiTarget::from_str(value)
}
}
impl From<InfoHash> for MultiTarget {
fn from(h: InfoHash) -> MultiTarget {
MultiTarget::Hash(SingleTarget::new(h.as_str()).unwrap())
}
}
impl From<SingleTarget> for MultiTarget {
fn from(value: SingleTarget) -> MultiTarget {
MultiTarget::Hash(value)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn singletarget_can_be_truncated() {
let target =
SingleTarget::new("abcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdef1234")
.unwrap();
let truncated = target.truncated();
assert_eq!(truncated.len(), 40);
assert_eq!(truncated, "abcdefabcdefabcdefabcdefabcdefabcdefabcd");
}
#[test]
fn singletarget_ignores_casing() {
assert_eq!(
SingleTarget::new("ABCDEFABCDEFABCDEFABCDEFABCDEFABCDEFABCDEFABCDEFABCDEFABCDEF1234")
.unwrap(),
SingleTarget::new("abcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdef1234")
.unwrap()
);
}
#[test]
fn deserialize_single_target() {
let s = "not a torrent";
assert!(serde_json::from_str::<SingleTarget>(&format!("\"{}\"", s)).is_err());
let s = "abcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdef1234";
assert!(serde_json::from_str::<SingleTarget>(&format!("\"{}\"", s)).is_ok());
}
#[test]
fn deserialize_ignores_casing() {
let s = "ABCDEFABCDEFABCDEFABCDEFABCDEFABCDEFABCDEFABCDEFABCDEFABCDEF1234";
let res = serde_json::from_str::<SingleTarget>(&format!("\"{}\"", s));
assert!(res.is_ok());
let target = res.unwrap();
assert_eq!(target, SingleTarget::new(&s.to_lowercase()).unwrap());
}
#[test]
fn deserialize_forbidden_chars() {
let s = "ABCDEFABCDEFABCDEFABCDEFABCDEFABCDEFABCDEFABCDEFABCDEFABCDEF....";
let res = serde_json::from_str::<SingleTarget>(&format!("\"{}\"", s));
assert!(res.is_err());
}
}