Skip to content

Commit

Permalink
feat: add client module; use ApiClient trait
Browse files Browse the repository at this point in the history
  • Loading branch information
mmmchimps committed Nov 15, 2023
1 parent fc47a9b commit 73fee0d
Show file tree
Hide file tree
Showing 8 changed files with 103 additions and 96 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
4 changes: 1 addition & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ You need a valid API key to use this library. Visit <https://steamcommunity.com/
## Example

```rust,no_run
use steamr::SteamClient;
use steamr::client::SteamClient;
use steamr::errors::SteamError;
use steamr::games::get_owned_games;
Expand All @@ -36,5 +36,3 @@ fn main() -> Result<(), SteamError> {
Ok(())
}
```

> 💡 You can find more examples on the crate's doc.rs pages.
63 changes: 63 additions & 0 deletions src/client.rs
Original file line number Diff line number Diff line change
@@ -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<T: Serialize>(
&self,
endpoint: &str,
query: Vec<(&str, T)>,
) -> Result<Value, SteamError>;
}

/// 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<T: Serialize>(
&self,
endpoint: &str,
query: Vec<(&str, T)>,
) -> Result<Value, SteamError> {
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 }
}
}
17 changes: 8 additions & 9 deletions src/friends.rs
Original file line number Diff line number Diff line change
@@ -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;

Expand Down Expand Up @@ -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> {
Expand All @@ -86,15 +87,13 @@ pub enum SteamRelationship {
/// }
/// ```
pub fn get_friends(client: &SteamClient, steam_id: &str) -> Result<Vec<Friend>, 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),
Expand Down
46 changes: 21 additions & 25 deletions src/games.rs
Original file line number Diff line number Diff line change
@@ -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;

Expand Down Expand Up @@ -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> {
Expand All @@ -85,19 +85,17 @@ impl std::fmt::Display for Game {
/// }
/// ```
pub fn get_owned_games(client: &SteamClient, steam_id: &str) -> Result<OwnedGames, SteamError> {
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),
Expand Down Expand Up @@ -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> {
Expand All @@ -172,19 +170,17 @@ pub fn get_game_news(
news_count: u16,
max_length: u16,
) -> Result<GameNews, SteamError> {
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),
Expand Down
51 changes: 1 addition & 50 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<T: Serialize>(
&self,
endpoint: &str,
query_params: Vec<(&str, T)>,
) -> Result<Response, SteamError> {
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(),
)),
}
}
}
8 changes: 4 additions & 4 deletions tests/test_friends.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use steamr::friends::get_friends;
use steamr::SteamClient;
use steamr::client::SteamClient;

#[test]
fn valid_games_response() {
Expand All @@ -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());
}
8 changes: 4 additions & 4 deletions tests/test_games.rs
Original file line number Diff line number Diff line change
@@ -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() {
Expand All @@ -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]
Expand All @@ -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);
}
}

0 comments on commit 73fee0d

Please sign in to comment.