1use app::App;
2use log::trace;
3use reqwest::{StatusCode, Url};
4use serde::{de::DeserializeOwned, Deserialize, Serialize};
5
6pub mod app;
7pub mod system;
8
9pub struct Protontweaks {
10 url: &'static str,
11}
12
13#[derive(Debug, Deserialize, Serialize)]
14pub struct AppsList {
15 pub sha: String,
16 pub short_sha: String,
17 pub apps: Vec<MicroApp>,
18}
19
20#[derive(Debug, Deserialize, Serialize)]
21pub struct MicroApp {
22 pub id: String,
23 pub name: String,
24}
25
26impl Protontweaks {
27 pub const fn new() -> Self {
28 Self {
29 url: "https://api.protontweaks.com/v4",
30 }
31 }
32
33 pub const fn new_with_url(url: &'static str) -> Self {
34 Self { url: url }
35 }
36
37 pub fn to_url(&self, endpoint: &str) -> Url {
38 let url = Url::parse(&self.url).unwrap();
39
40 return url.join(endpoint).unwrap();
41 }
42
43 async fn get<T: DeserializeOwned>(&self, url: &str) -> Result<T, String> {
44 let url = self.to_url(url);
45
46 trace!("Requesting apps from '{url}'...");
47
48 let response = reqwest::get(url.clone())
49 .await
50 .map_err(|error| error.to_string())?;
51
52 if response.status().is_success() {
53 response
54 .json::<T>()
55 .await
56 .map_err(|_| "Failed to parse apps.json".to_string())
57 } else {
58 match response.status() {
59 StatusCode::NOT_FOUND => {
60 Err(format!("Unable to locate file at '{}'.", url.to_string()))
61 }
62 _ => Err(response.error_for_status().unwrap_err().to_string()),
63 }
64 }
65 }
66
67 pub async fn try_apps_list(&self) -> Result<AppsList, String> {
68 self.get::<AppsList>("apps.json").await
69 }
70
71 pub async fn apps_list(&self) -> AppsList {
72 self.try_apps_list().await.unwrap()
73 }
74
75 pub async fn try_apps(&self) -> Result<Vec<MicroApp>, String> {
76 self.try_apps_list().await.map(|apps_list| apps_list.apps)
77 }
78
79 pub async fn apps(&self) -> Vec<MicroApp> {
80 self.try_apps().await.unwrap()
81 }
82
83 pub async fn try_app_ids(&self) -> Result<Vec<String>, String> {
84 self.try_apps()
85 .await
86 .map(|apps| apps.iter().map(|app| app.id.clone()).collect())
87 }
88
89 pub async fn app_ids(&self) -> Vec<String> {
90 self.try_app_ids().await.unwrap()
91 }
92
93 pub async fn try_app(&self, app_id: &str) -> Result<App, String> {
94 self.get::<App>(&format!("{app_id}.json")).await
95 }
96
97 pub async fn app(&self, app_id: &str) -> App {
98 self.try_app(app_id).await.unwrap()
99 }
100}
101
102#[cfg(test)]
103mod tests {
104 use super::*;
105
106 #[tokio::test]
107 async fn apps_list() {
108 let api = Protontweaks::new();
109
110 assert!(
111 api.try_apps_list().await.unwrap().apps.len() > 0,
112 "Should contain a list of apps"
113 );
114 assert!(
115 api.apps_list().await.apps.len() > 0,
116 "Should contain a list of apps"
117 );
118 }
119
120 #[tokio::test]
121 async fn apps() {
122 let api = Protontweaks::new();
123
124 assert!(
125 api.try_apps().await.unwrap().len() > 0,
126 "Should be a list of apps"
127 );
128 assert!(api.apps().await.len() > 0, "Should be a list of apps");
129 }
130
131 #[tokio::test]
132 async fn app_ids() {
133 let api = Protontweaks::new();
134
135 assert!(
136 api.try_app_ids().await.unwrap().len() > 0,
137 "Should be a list of app ids"
138 );
139 assert!(api.app_ids().await.len() > 0, "Should be a list of app ids");
140 }
141
142 #[tokio::test]
143 async fn try_app() {
144 let expected_id = "644930";
145 let api = Protontweaks::new();
146
147 let app = api.try_app(expected_id).await.unwrap();
148
149 assert_eq!(app.id, expected_id);
150 assert_eq!(app.issues.len(), 1);
151 assert_eq!(app.tweaks.tricks.len(), 1);
152 assert_eq!(app.tweaks.env.len(), 0);
153 assert_eq!(app.tweaks.settings.gamemode, Some(true));
154 assert_eq!(app.tweaks.settings.mangohud, Some(true));
155 }
156
157 #[tokio::test]
158 async fn app() {
159 let expected_id = "644930";
160 let api = Protontweaks::new();
161
162 let app = api.app(expected_id).await;
163
164 assert_eq!(app.id, expected_id);
165 assert_eq!(app.issues.len(), 1);
166 assert_eq!(app.tweaks.tricks.len(), 1);
167 assert_eq!(app.tweaks.env.len(), 0);
168 assert_eq!(app.tweaks.settings.gamemode, Some(true));
169 assert_eq!(app.tweaks.settings.mangohud, Some(true));
170 }
171}