11use super :: { Api , Result } ;
2- use serde_json:: { json, Value } ;
2+ use reqwest:: RequestBuilder ;
3+ use serde_json:: Value ;
34
4- pub struct Client {
5+ /// Client for accessing Listen Notes API.
6+ pub struct Client < ' a > {
7+ /// HTTP client.
58 client : reqwest:: Client ,
6- api : Api ,
9+ /// API context.
10+ api : Api < ' a > ,
711}
812
9- impl Client {
10- pub fn new ( client : reqwest:: Client , id : Option < String > ) -> Client {
13+ impl Client < ' _ > {
14+ /// Creates new Listen API Client.
15+ ///
16+ /// To access production API:
17+ /// ```
18+ /// let client = podcast_api::Client::new(reqwest::Client::new(), Some("YOUR-API-KEY"));
19+ /// ```
20+ /// To access mock API:
21+ /// ```
22+ /// let client = podcast_api::Client::new(reqwest::Client::new(), None);
23+ /// ```
24+ pub fn new ( client : reqwest:: Client , id : Option < & str > ) -> Client {
1125 Client {
1226 client,
1327 api : if let Some ( id) = id {
@@ -18,59 +32,105 @@ impl Client {
1832 }
1933 }
2034
35+ /// Calls [`GET /search`](https://www.listennotes.com/api/docs/#get-api-v2-search) with supplied parameters.
2136 pub async fn search ( & self , parameters : & Value ) -> Result < Value > {
2237 self . get ( "search" , parameters) . await
2338 }
2439
40+ /// Calls [`GET /typeahead`](https://www.listennotes.com/api/docs/#get-api-v2-typeahead) with supplied parameters.
2541 pub async fn typeahead ( & self , parameters : & Value ) -> Result < Value > {
2642 self . get ( "typeahead" , parameters) . await
2743 }
2844
29- pub async fn episode_by_id ( & self , id : & str , parameters : & Value ) -> Result < Value > {
30- self . get ( & format ! ( "episodes/{}" , id) , parameters) . await
45+ /// Calls [`GET /best_podcasts`](https://www.listennotes.com/api/docs/#get-api-v2-best_podcasts) with supplied parameters.
46+ pub async fn fetch_best_podcasts ( & self , parameters : & Value ) -> Result < Value > {
47+ self . get ( "best_podcasts" , parameters) . await
48+ }
49+
50+ /// Calls [`GET /podcasts/{id}`](https://www.listennotes.com/api/docs/#get-api-v2-podcasts-id) with supplied parameters.
51+ pub async fn fetch_podcast_by_id ( & self , id : & str , parameters : & Value ) -> Result < Value > {
52+ self . get ( & format ! ( "podcasts/{}" , id) , parameters) . await
3153 }
3254
33- pub async fn episodes ( & self , ids : & [ & str ] , parameters : & Value ) -> Result < Value > {
34- self . post ( "episodes" , & parameters. with ( "ids" , & ids . join ( "," ) . as_str ( ) ) )
35- . await
55+ /// Calls [`POST /podcasts`](https://www.listennotes.com/api/docs/#post-api-v2-podcasts) with supplied parameters.
56+ pub async fn batch_fetch_podcasts ( & self , parameters : & Value ) -> Result < Value > {
57+ self . post ( "podcasts" , parameters ) . await
3658 }
3759
38- pub async fn genres ( & self , parameters : & Value ) -> Result < Value > {
39- self . get ( "genres" , parameters) . await
60+ /// Calls [`GET /episodes/{id}`](https://www.listennotes.com/api/docs/#get-api-v2-episodes-id) with supplied parameters.
61+ pub async fn fetch_episode_by_id ( & self , id : & str , parameters : & Value ) -> Result < Value > {
62+ self . get ( & format ! ( "episodes/{}" , id) , parameters) . await
63+ }
64+
65+ /// Calls [`POST /episodes`](https://www.listennotes.com/api/docs/#post-api-v2-episodes) with supplied parameters.
66+ pub async fn batch_fetch_episodes ( & self , parameters : & Value ) -> Result < Value > {
67+ self . post ( "episodes" , parameters) . await
4068 }
4169
4270 async fn get ( & self , endpoint : & str , parameters : & Value ) -> Result < Value > {
43- Ok ( self
71+ let request = self
4472 . client
4573 . get ( format ! ( "{}/{}" , self . api. url( ) , endpoint) )
46- . query ( parameters)
47- . send ( )
48- . await ?
49- . json ( )
50- . await ?)
74+ . query ( parameters) ;
75+
76+ Ok ( self . request ( request) . await ?)
5177 }
5278
5379 async fn post ( & self , endpoint : & str , parameters : & Value ) -> Result < Value > {
54- Ok ( self
80+ let request = self
5581 . client
5682 . post ( format ! ( "{}/{}" , self . api. url( ) , endpoint) )
5783 . header ( "Content-Type" , "application/x-www-form-urlencoded" )
58- . body ( serde_json:: to_string ( & parameters) ?) // TODO: switch to URL encoding
59- . send ( )
60- . await ?
61- . json ( )
62- . await ?)
84+ . body ( Self :: urlencoded_from_json ( parameters) ) ;
85+
86+ Ok ( self . request ( request) . await ?)
6387 }
64- }
6588
66- trait AddField {
67- fn with ( & self , key : & str , value : & str ) -> Self ;
89+ async fn request ( & self , request : RequestBuilder ) -> Result < Value > {
90+ Ok ( if let Api :: Production ( key) = self . api {
91+ request. header ( "X-ListenAPI-Key" , key)
92+ } else {
93+ request
94+ }
95+ . send ( )
96+ . await ?
97+ . json ( )
98+ . await ?)
99+ }
100+
101+ fn urlencoded_from_json ( json : & Value ) -> String {
102+ if let Some ( v) = json. as_object ( ) {
103+ v. iter ( )
104+ . map ( |( key, value) | {
105+ format ! (
106+ "{}={}" ,
107+ key,
108+ match value {
109+ Value :: String ( s) => s. to_owned( ) , // serde_json String(_) formatter includes the quotations marks, this doesn't
110+ _ => format!( "{}" , value) ,
111+ }
112+ )
113+ } )
114+ . collect :: < Vec < String > > ( )
115+ . join ( "&" )
116+ } else {
117+ String :: new ( )
118+ }
119+ }
68120}
69121
70- impl AddField for Value {
71- fn with ( & self , key : & str , value : & str ) -> Self {
72- let mut p = self . clone ( ) ;
73- p[ key] = json ! ( value) ;
74- p
122+ #[ cfg( test) ]
123+ mod tests {
124+ use serde_json:: json;
125+ #[ test]
126+ fn urlencoded_from_json ( ) {
127+ assert_eq ! (
128+ super :: Client :: urlencoded_from_json( & json!( {
129+ "a" : 1 ,
130+ "b" : true ,
131+ "c" : "test_string"
132+ } ) ) ,
133+ "a=1&b=true&c=test_string"
134+ ) ;
75135 }
76136}
0 commit comments