diff --git a/Cargo.toml b/Cargo.toml index ad30e56..5d6ea58 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,7 @@ license = "Apache-2.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -reqwest = {version = "0.11.22", features = ["blocking"] } +reqwest = {version = "0.11.22", features = ["blocking", "json"] } serde = { version = "1.0.192", features = ["derive"] } serde_json = "1.0.108" thiserror = "1.0.50" diff --git a/README.md b/README.md index 7f97321..d6e49fd 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ You need a valid API key to use this library. Visit Result<(), SteamError> { Ok(()) } ``` - -> 💡 You can find more examples on the crate's doc.rs pages. diff --git a/src/client.rs b/src/client.rs new file mode 100644 index 0000000..0582f3b --- /dev/null +++ b/src/client.rs @@ -0,0 +1,63 @@ +//! The Steam API client + +use crate::errors::SteamError; +use reqwest::blocking::Client; +use reqwest::StatusCode; +use serde::Serialize; +use serde_json::Value; + +/// Common functions for a Steam API client. +pub trait ApiClient { + /// Send a GET request and return JSON + fn get_request( + &self, + endpoint: &str, + query: Vec<(&str, T)>, + ) -> Result; +} + +/// This struct holds the blocking reqwest client and is used to interact with the API. +pub struct SteamClient { + /// A [`reqwest::blocking`] HTTP client + client: Client, + /// The dev's Steam API key + api_key: String, +} + +impl ApiClient for SteamClient { + fn get_request( + &self, + endpoint: &str, + query: Vec<(&str, T)>, + ) -> Result { + let response = self + .client + .get(endpoint) + .query(&[("key", self.api_key.clone())]) + .query(&query) + .send(); + + match response { + Ok(r) => match r.status() { + StatusCode::OK => Ok(r.json().unwrap()), // we trust steam that we'll actually get json w/ a 200 response, so unwrap() is good enough + StatusCode::UNAUTHORIZED => { + Err(SteamError::FailedRequest("Unauthorized. Either you have used an invalid API key, or the data you wanted to access is private".to_string())) + } + _ => Err(SteamError::FailedRequest( + "Steam could not process your request. Double-check your provided Steam IDs.".to_string(), + )), + }, + Err(_) => Err(SteamError::FailedRequest( + "Something went wrong with your request".to_string(), + )), + } + } +} + +impl SteamClient { + /// Returns a new SteamClient instance. + pub fn new(api_key: String) -> Self { + let client = reqwest::blocking::Client::new(); + SteamClient { client, api_key } + } +} diff --git a/src/friends.rs b/src/friends.rs index c887bbf..09e63c2 100644 --- a/src/friends.rs +++ b/src/friends.rs @@ -1,6 +1,7 @@ //! Functions to handle any friends-related data -use crate::{SteamClient, SteamError}; +use crate::client::{ApiClient, SteamClient}; +use crate::errors::SteamError; use serde::Deserialize; use std::fmt::Formatter; @@ -61,7 +62,7 @@ pub enum SteamRelationship { /// // This specific example will not work since the API key is invalid and we're using "?". Also, /// // chrono is not part of this lib's dependencies. /// -/// # use steamr::SteamClient; +/// # use steamr::client::SteamClient; /// # use steamr::friends::get_friends; /// # use steamr::errors::SteamError; /// fn main() -> Result<(), SteamError> { @@ -86,15 +87,13 @@ pub enum SteamRelationship { /// } /// ``` pub fn get_friends(client: &SteamClient, steam_id: &str) -> Result, SteamError> { - let response = client - .send_steam_request( - ENDPOINT_GET_FRIENDLIST, - vec![("steamid", steam_id), ("relationship", "friend")], - )? - .text()?; + let response = client.get_request( + ENDPOINT_GET_FRIENDLIST, + vec![("steamid", steam_id), ("relationship", "friend")], + )?; let _res: FriendsResponse = - serde_json::from_str(&response).unwrap_or(FriendsResponse { response: None }); + serde_json::from_value(response).unwrap_or(FriendsResponse { response: None }); match _res.response { None => Err(SteamError::NoData), diff --git a/src/games.rs b/src/games.rs index cc0e558..1320206 100644 --- a/src/games.rs +++ b/src/games.rs @@ -1,7 +1,7 @@ //! Contains all functionalities around games +use crate::client::{ApiClient, SteamClient}; use crate::errors::SteamError; -use crate::SteamClient; use serde::Deserialize; use std::fmt::Formatter; @@ -68,7 +68,7 @@ impl std::fmt::Display for Game { /// ```no_run /// // This specific example will not work since the API key is invalid and we're using "?". /// -/// # use steamr::SteamClient; +/// # use steamr::client::SteamClient; /// # use steamr::games::get_owned_games; /// # use steamr::errors::SteamError; /// fn main() -> Result<(), SteamError> { @@ -85,19 +85,17 @@ impl std::fmt::Display for Game { /// } /// ``` pub fn get_owned_games(client: &SteamClient, steam_id: &str) -> Result { - let response = client - .send_steam_request( - ENDPOINT_OWNED_GAMES, - vec![ - ("steamid", steam_id), - ("include_appInfo", "true"), - ("include_played_free_games", "true"), - ], - )? - .text()?; + let response = client.get_request( + ENDPOINT_OWNED_GAMES, + vec![ + ("steamid", steam_id), + ("include_appInfo", "true"), + ("include_played_free_games", "true"), + ], + )?; let _res: OwnedGamesResponse = - serde_json::from_str(&response).unwrap_or(OwnedGamesResponse { response: None }); + serde_json::from_value(response).unwrap_or(OwnedGamesResponse { response: None }); match _res.response { None => Err(SteamError::NoData), @@ -152,7 +150,7 @@ pub struct News { /// Example: /// /// ``` -/// # use steamr::SteamClient; +/// # use steamr::client::SteamClient; /// # use steamr::games::get_game_news; /// # use steamr::errors::SteamError; /// fn main() -> Result<(), SteamError> { @@ -172,19 +170,17 @@ pub fn get_game_news( news_count: u16, max_length: u16, ) -> Result { - let response = client - .send_steam_request( - ENDPOINT_GAME_NEWS, - vec![ - ("appid", game_id), - ("count", news_count.to_string()), - ("maxlength", max_length.to_string()), - ], - )? - .text()?; + let response = client.get_request( + ENDPOINT_GAME_NEWS, + vec![ + ("appid", game_id), + ("count", news_count.to_string()), + ("maxlength", max_length.to_string()), + ], + )?; let _res: GameNewsResponse = - serde_json::from_str(&response).unwrap_or(GameNewsResponse { response: None }); + serde_json::from_value(response).unwrap_or(GameNewsResponse { response: None }); match _res.response { None => Err(SteamError::NoData), diff --git a/src/lib.rs b/src/lib.rs index 75062ed..d33a605 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,56 +2,7 @@ #![deny(missing_docs)] #![deny(rustdoc::missing_doc_code_examples)] -use crate::errors::SteamError; -use reqwest::blocking::{Client, Response}; -use reqwest::StatusCode; -use serde::Serialize; - +pub mod client; pub mod errors; pub mod friends; pub mod games; - -/// This struct holds the blocking reqwest client and is used to interact with the API. -pub struct SteamClient { - /// A [`reqwest::blocking`] HTTP client - client: Client, - /// The dev's Steam API key - api_key: String, -} - -impl SteamClient { - /// Returns a new SteamClient instance. - pub fn new(api_key: String) -> Self { - let client = reqwest::blocking::Client::new(); - SteamClient { client, api_key } - } - - /// A common function used to send requests to Steam's API and to return JSON data. - fn send_steam_request( - &self, - endpoint: &str, - query_params: Vec<(&str, T)>, - ) -> Result { - let response = self - .client - .get(endpoint) - .query(&[("key", self.api_key.clone())]) - .query(&query_params) - .send(); - - match response { - Ok(r) => match r.status() { - StatusCode::OK => Ok(r), - StatusCode::UNAUTHORIZED => { - Err(SteamError::FailedRequest("Unauthorized. Either you have used an invalid API key, or the data you wanted to access is private".to_string())) - } - _ => Err(SteamError::FailedRequest( - "Steam could not process your request. Double-check your provided Steam IDs.".to_string(), - )), - }, - Err(_) => Err(SteamError::FailedRequest( - "Something went wrong with your request".to_string(), - )), - } - } -} diff --git a/tests/test_friends.rs b/tests/test_friends.rs index ca191c9..4df3614 100644 --- a/tests/test_friends.rs +++ b/tests/test_friends.rs @@ -1,5 +1,5 @@ use steamr::friends::get_friends; -use steamr::SteamClient; +use steamr::client::SteamClient; #[test] fn valid_games_response() { @@ -10,6 +10,6 @@ fn valid_games_response() { let test_friends = get_friends(&test_client, &test_steam_id).unwrap_or_else(|e| panic!("{:?}", e)); - assert!(test_friends.len() > 0); - assert!(test_friends[0].steam_id.len() > 0); -} + assert!(!test_friends.is_empty()); + assert!(!test_friends[0].steam_id.is_empty()); +} \ No newline at end of file diff --git a/tests/test_games.rs b/tests/test_games.rs index 8569872..466e2a4 100644 --- a/tests/test_games.rs +++ b/tests/test_games.rs @@ -1,5 +1,5 @@ use steamr::games::{get_game_news, get_owned_games}; -use steamr::SteamClient; +use steamr::client::SteamClient; #[test] fn games_response_is_valid() { @@ -10,8 +10,8 @@ fn games_response_is_valid() { let test_steam_lib = get_owned_games(&test_client, &test_steam_id).unwrap_or_else(|e| panic!("{:?}", e)); - assert!(test_steam_lib.games.len() > 0); - assert!(test_steam_lib.games[0].name.len() > 0); + assert!(!test_steam_lib.games.is_empty()); + assert!(!test_steam_lib.games[0].name.is_empty()); } #[test] @@ -24,4 +24,4 @@ fn game_news_response_is_valid() { assert!(test_news.count >= test_news.game_news.len().try_into().unwrap()); assert_eq!(test_news.game_news.len(), 5); -} +} \ No newline at end of file