Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
form_urlencoded = "1"
http = "0.2"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
tokio = { version = "1", features = ["full"] }
Expand Down
10 changes: 7 additions & 3 deletions examples/sample/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ async fn main() {
let api_key = None;

// Create client
let client = podcast_api::Client::new(reqwest::Client::new(), api_key);
let client = podcast_api::Client::new(api_key);

// Call API
match client
Expand All @@ -18,8 +18,12 @@ async fn main() {
{
Ok(response) => {
println!("Successfully called \"typeahead\" endpoint.");
println!("Response Body:");
println!("{:?}", response);
if let Ok(body) = response.json().await {
println!("Response Body:");
println!("{:?}", body);
} else {
println!("Response body JSON data parsing error.")
}
}
Err(err) => {
println!("Error calling \"typeahead\" endpoint:");
Expand Down
87 changes: 69 additions & 18 deletions src/client.rs
Original file line number Diff line number Diff line change
@@ -1,73 +1,121 @@
use super::{Api, Result};
use reqwest::RequestBuilder;
use serde_json::Value;
use std::time::Duration;

static DEFAULT_USER_AGENT: &str = "api-podcast-rust";

/// Client for accessing Listen Notes API.
pub struct Client<'a> {
/// HTTP client.
client: reqwest::Client,
/// API context.
api: Api<'a>,
/// User Agent Header for API calls.
user_agent: &'a str,
}

#[derive(Debug)]
/// Response and request context for API call.
pub struct Response {
/// HTTP response.
pub response: reqwest::Response,
/// HTTP request that resulted in this response.
pub request: reqwest::Request,
}

impl Response {
/// Get JSON data object from [`reqwest::Response`].
pub async fn json(self) -> Result<Value> {
Ok(self.response.json().await?)
}
}

impl Client<'_> {
/// Creates new Listen API Client.
///
/// Uses default HTTP client with 30 second timeouts.
///
/// To access production API:
/// ```
/// let client = podcast_api::Client::new(reqwest::Client::new(), Some("YOUR-API-KEY"));
/// let client = podcast_api::Client::new(Some("YOUR-API-KEY"));
/// ```
/// To access mock API:
/// ```
/// let client = podcast_api::Client::new(reqwest::Client::new(), None);
/// let client = podcast_api::Client::new(None);
/// ```
pub fn new(client: reqwest::Client, id: Option<&str>) -> Client {
pub fn new(id: Option<&str>) -> Client {
Client {
client: reqwest::ClientBuilder::new()
.timeout(Duration::from_secs(30))
.build()
.expect("Client::new()"),
api: if let Some(id) = id {
Api::Production(id)
} else {
Api::Mock
},
user_agent: DEFAULT_USER_AGENT,
}
}

/// Creates new Listen API Client with user provided HTTP Client.
pub fn new_custom<'a>(
client: reqwest::Client,
id: Option<&'a str>,
user_agent: Option<&'a str>,
) -> Client<'a> {
Client {
client,
api: if let Some(id) = id {
Api::Production(id)
} else {
Api::Mock
},
user_agent: if let Some(user_agent) = user_agent {
user_agent
} else {
DEFAULT_USER_AGENT
},
}
}

/// Calls [`GET /search`](https://www.listennotes.com/api/docs/#get-api-v2-search) with supplied parameters.
pub async fn search(&self, parameters: &Value) -> Result<Value> {
pub async fn search(&self, parameters: &Value) -> Result<Response> {
self.get("search", parameters).await
}

/// Calls [`GET /typeahead`](https://www.listennotes.com/api/docs/#get-api-v2-typeahead) with supplied parameters.
pub async fn typeahead(&self, parameters: &Value) -> Result<Value> {
pub async fn typeahead(&self, parameters: &Value) -> Result<Response> {
self.get("typeahead", parameters).await
}

/// Calls [`GET /best_podcasts`](https://www.listennotes.com/api/docs/#get-api-v2-best_podcasts) with supplied parameters.
pub async fn fetch_best_podcasts(&self, parameters: &Value) -> Result<Value> {
pub async fn fetch_best_podcasts(&self, parameters: &Value) -> Result<Response> {
self.get("best_podcasts", parameters).await
}

/// Calls [`GET /podcasts/{id}`](https://www.listennotes.com/api/docs/#get-api-v2-podcasts-id) with supplied parameters.
pub async fn fetch_podcast_by_id(&self, id: &str, parameters: &Value) -> Result<Value> {
pub async fn fetch_podcast_by_id(&self, id: &str, parameters: &Value) -> Result<Response> {
self.get(&format!("podcasts/{}", id), parameters).await
}

/// Calls [`POST /podcasts`](https://www.listennotes.com/api/docs/#post-api-v2-podcasts) with supplied parameters.
pub async fn batch_fetch_podcasts(&self, parameters: &Value) -> Result<Value> {
pub async fn batch_fetch_podcasts(&self, parameters: &Value) -> Result<Response> {
self.post("podcasts", parameters).await
}

/// Calls [`GET /episodes/{id}`](https://www.listennotes.com/api/docs/#get-api-v2-episodes-id) with supplied parameters.
pub async fn fetch_episode_by_id(&self, id: &str, parameters: &Value) -> Result<Value> {
pub async fn fetch_episode_by_id(&self, id: &str, parameters: &Value) -> Result<Response> {
self.get(&format!("episodes/{}", id), parameters).await
}

/// Calls [`POST /episodes`](https://www.listennotes.com/api/docs/#post-api-v2-episodes) with supplied parameters.
pub async fn batch_fetch_episodes(&self, parameters: &Value) -> Result<Value> {
pub async fn batch_fetch_episodes(&self, parameters: &Value) -> Result<Response> {
self.post("episodes", parameters).await
}

async fn get(&self, endpoint: &str, parameters: &Value) -> Result<Value> {
async fn get(&self, endpoint: &str, parameters: &Value) -> Result<Response> {
let request = self
.client
.get(format!("{}/{}", self.api.url(), endpoint))
Expand All @@ -76,7 +124,7 @@ impl Client<'_> {
Ok(self.request(request).await?)
}

async fn post(&self, endpoint: &str, parameters: &Value) -> Result<Value> {
async fn post(&self, endpoint: &str, parameters: &Value) -> Result<Response> {
let request = self
.client
.post(format!("{}/{}", self.api.url(), endpoint))
Expand All @@ -86,16 +134,19 @@ impl Client<'_> {
Ok(self.request(request).await?)
}

async fn request(&self, request: RequestBuilder) -> Result<Value> {
Ok(if let Api::Production(key) = self.api {
async fn request(&self, request: RequestBuilder) -> Result<Response> {
let request = if let Api::Production(key) = self.api {
request.header("X-ListenAPI-Key", key)
} else {
request
}
.send()
.await?
.json()
.await?)
.header("User-Agent", self.user_agent)
.build()?;

Ok(Response {
response: self.client.execute(request.try_clone().expect("Error can remain unhandled because we're not using streams, which are the try_clone fail condition")).await?,
request,
})
}

fn urlencoded_from_json(json: &Value) -> String {
Expand Down
3 changes: 2 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
//! let api_key = None;
//!
//! // Create client
//! let client = podcast_api::Client::new(reqwest::Client::new(), api_key);
//! let client = podcast_api::Client::new(api_key);
//!
//! // Call API
//! match client
Expand Down Expand Up @@ -42,6 +42,7 @@ mod error;
use api::Api;

pub use client::Client;
pub use client::Response;
pub use error::Error;
/// Result for API calls from [`Client`]
pub type Result<T> = std::result::Result<T, error::Error>;
Loading