1use reqwest::{Client, StatusCode};
2use thiserror::Error;
3use serde::{Deserialize, de::DeserializeOwned};
4
5mod parsers;
6pub mod endpoints;
7
8pub struct Itchio {
10 client: Client,
11 key: String,
12}
13
14impl Itchio {
15 pub fn new(key: String) -> Self {
17 Self {
18 client: Client::new(),
19 key,
20 }
21 }
22
23 pub async fn request<T: DeserializeOwned>(&self, endpoint: String) -> Result<T, ItchioError> {
25 let url = format!("https://itch.io/api/1/key/{}", endpoint);
26 let response = self
27 .client
28 .get(&url)
29 .header("Authorization", format!("Bearer {}", self.key))
30 .header("User-Agent", "itchio-api (https://codeberg.org/Taevas/itchio-api)")
31 .send()
32 .await
33 .map_err(|err| {
34 match err.status() {
35 Some(status) => match status {
36 StatusCode::NOT_FOUND => ItchioError::BadEndpoint,
37 StatusCode::TOO_MANY_REQUESTS => ItchioError::RateLimited,
38 _ => ItchioError::RequestFailed(err),
39 }
40 None => ItchioError::RequestFailed(err),
41 }
42 })?;
43
44 let text = response
47 .text()
48 .await?;
49
50 if let Ok(bad_response) = serde_json::from_str::<ErrorResponseWrapped>(&text) {
51 let error = match bad_response.errors.zero.as_str() {
52 "invalid key" => ItchioError::BadKey,
53 "missing authorization header" => ItchioError::BadKey,
54 "invalid api endpoint" => ItchioError::BadEndpoint, other => ItchioError::InternalError(other.to_string()),
56 };
57 return Err(error);
58 }
59
60 Ok(serde_json::from_str::<T>(&text)?)
61 }
62}
63
64#[derive(Deserialize)]
65struct ErrorResponseWrapped {
66 errors: ErrorResponse
67}
68
69#[derive(Deserialize)]
70struct ErrorResponse {
71 #[serde(rename = "0")]
72 zero: String
73}
74
75#[derive(Error, Debug)]
77pub enum ItchioError {
78 #[error("HTTP request failed: {0}")]
80 RequestFailed(#[from] reqwest::Error),
81 #[error("Failed to parse JSON: {0}")]
83 JsonParseError(#[from] serde_json::Error),
84 #[error("An unexpected error happened: {0}")]
86 InternalError(String),
87 #[error("Authentication failed.")]
89 BadKey,
90 #[error("The requested endpoint doesn't exist.")]
92 BadEndpoint,
93 #[error("The server is rate limiting us.")]
95 RateLimited,
96}