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); } }