1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
use hyper::{body::HttpBody, Client};
use hyper_tls::HttpsConnector;
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum Status {
    Ok,
    Other(String),
}

#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
pub struct Torrent {
    pub url: String,
    pub hash: String,
    pub quality: String,
    #[serde(rename = "type")]
    pub _type: String,
    pub seeds: u32,
    pub peers: u32,
    pub size: String,
    pub size_bytes: u64,
    pub date_uploaded: String,
    pub date_uploaded_unix: u64,
}

#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct Movie {
    pub id: u32,
    pub url: String,
    pub imdb_code: String,
    pub title: String,
    pub title_english: String,
    pub title_long: String,
    pub slug: String,
    pub year: u32,
    pub rating: f32,
    pub runtime: u32,
    pub genres: Vec<String>,
    pub summary: String,
    pub description_full: String,
    pub synopsis: String,
    pub yt_trailer_code: String,
    pub language: String,
    pub mpa_rating: String,
    pub background_image: String,
    pub background_image_original: String,
    pub small_cover_image: String,
    pub medium_cover_image: String,
    pub large_cover_image: String,
    pub state: Status,
    pub torrents: Vec<Torrent>,
    pub date_uploaded: String,
    pub date_uploaded_unix: u64,
}

#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct MovieList {
    pub movie_count: u32,
    pub limit: u32,
    pub page_number: u32,
    pub movies: Vec<Movie>,
}

#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
#[serde(untagged)]
pub enum Data {
    MovieList(MovieList),
}

#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct Response {
    pub status: Status,
    pub status_message: String,
    pub data: Option<Data>,
}

pub async fn list_movies(
    query_term: &str,
) -> Result<MovieList, Box<dyn std::error::Error + Send + Sync>> {
    let https = HttpsConnector::new();
    let client = Client::builder().build::<_, hyper::Body>(https);

    let url = format!(
        "https://yts.mx/api/v2/list_movies.json?query_term={}",
        query_term
    );
    let mut res = client.get(url.parse()?).await?;
    let mut bytes = Vec::new();
    while let Some(next) = res.data().await {
        let chunk = next?;
        bytes.extend(chunk);
    }
    let body = String::from_utf8(bytes)?;
    let response: Response = serde_json::from_str(&body).unwrap();
    if let Status::Other(status) = response.status {
        return Err(format!("{}: {}", status, response.status_message).into());
    }
    let data = response.data.ok_or("Data missing")?;
    match data {
        Data::MovieList(movie_list) => Ok(movie_list),
    }
}

#[cfg(test)]
mod tests {
    static TEST_DATA: &str = include_str!("test/test.json");

    use super::*;

    #[test]
    fn deserialize_test_data() {
        let response: Response = serde_json::from_str(TEST_DATA).unwrap();
        assert_eq!(response.status, Status::Ok);
        assert_eq!(response.status_message, "Query was successful");
        let data = response.data.unwrap();
        let movie_list = match data {
            Data::MovieList(movie_list) => movie_list,
        };
        assert_eq!(movie_list.movie_count, 10);
        assert_eq!(movie_list.limit, 20);
        assert_eq!(movie_list.page_number, 1);
        assert_eq!(movie_list.movies.len(), 10);
    }
}