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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
#[macro_use]
extern crate serde;

use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC};
use thiserror::Error;

#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct SearchResponse {
    #[serde(rename = "resultCount")]
    pub result_count: usize,
    pub results: Vec<SearchResult>,
}

#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct SearchResult {
    #[serde(rename = "wrapperType")]
    pub wrapper_type: Option<WrapperType>,
    pub kind: Option<Kind>,
    #[serde(rename = "artistId")]
    pub artist_id: Option<usize>,
    #[serde(rename = "collectionId")]
    pub collection_id: Option<usize>,
    #[serde(rename = "trackId")]
    pub track_id: Option<usize>,
    #[serde(rename = "artistName")]
    pub artist_name: Option<String>,
    #[serde(rename = "collectionName")]
    pub collection_name: Option<String>,
    #[serde(rename = "trackName")]
    pub track_name: Option<String>,
    #[serde(rename = "collectionCensoredName")]
    pub collection_censored_name: Option<String>,
    #[serde(rename = "trackCensoredName")]
    pub track_censored_name: Option<String>,
    #[serde(rename = "artistViewUrl")]
    pub artist_view_url: Option<String>,
    #[serde(rename = "collectionViewUrl")]
    pub collection_view_url: Option<String>,
    #[serde(rename = "feedUrl")]
    pub feed_url: Option<String>,
    #[serde(rename = "trackViewUrl")]
    pub track_view_url: Option<String>,
    #[serde(rename = "artworkUrl30")]
    pub artwork_url30: Option<String>,
    #[serde(rename = "artworkUrl60")]
    pub artwork_url60: Option<String>,
    #[serde(rename = "artworkUrl100")]
    pub artwork_url100: Option<String>,
    #[serde(rename = "collectionPrice")]
    pub collection_price: Option<f64>,
    #[serde(rename = "trackPrice")]
    pub track_price: Option<f64>,
    #[serde(rename = "trackRentalPrice")]
    pub track_rental_price: Option<f64>,
    #[serde(rename = "collectionHdPrice")]
    pub collection_hd_price: Option<f64>,
    #[serde(rename = "trackHdPrice")]
    pub track_hd_price: Option<f64>,
    #[serde(rename = "trackHdRentalPrice")]
    pub track_hd_rental_price: Option<f64>,
    #[serde(rename = "releaseDate")]
    pub release_date: Option<String>,
    #[serde(rename = "collectionExplicitness")]
    pub collection_explicitness: Option<Explicitness>,
    #[serde(rename = "trackExplicitness")]
    pub track_explicitness: Option<Explicitness>,
    #[serde(rename = "trackCount")]
    pub track_count: Option<usize>,
    #[serde(with = "serde_with::rust::display_fromstr")]
    pub country: celes::Country,
    pub currency: Option<iso_currency::Currency>,
    #[serde(rename = "primaryGenreName")]
    pub primary_genre_name: Option<String>,
    #[serde(rename = "contentAdvisoryRating")]
    pub content_advisory_rating: Option<ContentAdvisoryRating>,
    #[serde(rename = "artworkUrl600")]
    pub artwork_url600: Option<String>,
    #[serde(rename = "genreIds")]
    pub genre_ids: Option<Vec<String>>,
    pub genres: Option<Vec<String>>,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub enum Explicitness {
    #[serde(rename = "cleaned")]
    Cleaned,
    #[serde(rename = "explicit")]
    Explicit,
    #[serde(rename = "notExplicit")]
    NotExplicit,
}

#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub enum ContentAdvisoryRating {
    Clean,
    Explicit,
}

#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub enum Kind {
    #[serde(rename = "podcast")]
    Podcast,
}

#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub enum WrapperType {
    #[serde(rename = "track")]
    Track,
    #[serde(rename = "collection")]
    Collection,
    #[serde(rename = "artist")]
    Artist,
}

#[derive(Error, Debug)]
pub enum PodcastSearchError {
    #[error("Failed to fetch information for itunes API {source:?}")]
    FetchError{
        #[from]
        source: reqwest::Error
    },
    #[error("Failed to parse response from itunes API {source:?}")]
    ParseError {
        #[from]
        source: serde_json::Error
    },
    #[error("An unknown error occurred!")]
    Unknown,
}

pub async fn search(terms: &str) -> Result<SearchResponse, PodcastSearchError> {
    let encoded: String = utf8_percent_encode(terms, NON_ALPHANUMERIC).to_string();
    let url =  format!("https://itunes.apple.com/search?media=podcast&entity=podcast&term={}", encoded);
    let resp = reqwest::get(&url).await?.json::<SearchResponse>().await?;
    Ok(resp)
}

#[cfg(test)]
mod tests {
    #[test]
    fn it_works() {
        assert_eq!(2 + 2, 4);
    }
}