playground_api/client.rs
1use crate::{endpoints::*, error::Error};
2use url::Url;
3
4/// A client for interacting with the Rust playground API.
5///
6/// Holds the base URL and the `reqwest::Client` struct for all requests.
7#[derive(Clone)]
8pub struct Client {
9 url: Url,
10 client: reqwest::Client,
11}
12
13impl Client {
14 /// Creates a new `Client` instance with the given base URL.
15 ///
16 /// Parses the provided string into a `Url`. Returns an error if the URL is invalid.
17 ///
18 /// # Arguments
19 ///
20 /// * `url` - A string slice representing the base URL of the Rust playground API.
21 ///
22 /// # Returns
23 ///
24 /// * `Result<Client, Error>` - On success, returns a `Client` configured with the parsed URL.
25 /// On failure, returns an `Error` if the URL string is invalid.
26 pub fn new(url: &str) -> Result<Client, Error> {
27 let url = Url::parse(url)?;
28 let client = reqwest::Client::new();
29 Ok(Client { url, client })
30 }
31
32 /// Sends a code execution request to the Rust playground and returns the result.
33 ///
34 /// This asynchronous method takes and [`ExecuteRequest`] struct containing the code
35 /// execution parameters, sends it to the appropriate endpoint on the Rust playground
36 /// via a POST request, and returns the execution result.
37 ///
38 /// # Arguments
39 ///
40 /// * `request` - A reference to an [`ExecuteRequest`] that includes the code snippet
41 /// and configuration options such as edition, crate type, and whether to run or compile.
42 ///
43 /// # Returns
44 ///
45 /// * `Result<ExecuteResponse, Error>` - On success, returns an [`ExecuteResponse`] containing
46 /// the output, errors, and status from the Rust playground. On failure, returns an [`Error`].
47 ///
48 /// # Errors
49 ///
50 /// This function will return an error if the HTTP request fails, if the response cannot be parsed,
51 /// or if the playground service is unavailable.
52 pub async fn execute(&self, request: &ExecuteRequest) -> Result<ExecuteResponse, Error> {
53 self.post(request).await
54 }
55
56 /// Sends a code compilation request to the Rust playground and returns the result.
57 ///
58 /// This asynchronous method takes a [`CompileRequest`] containing the code and
59 /// compilation parameters, sends it to the Rust playground's compile endpoint,
60 /// and returns the compilation result.
61 ///
62 /// # Arguments
63 ///
64 /// * `request` - A reference to a [`CompileRequest`] that includes the code and metadata
65 /// such as the toolchain edition, crate type, target, and any compiler settings.
66 ///
67 /// # Returns
68 ///
69 /// * `Result<CompileResponse, Error>` - On success, returns a [`CompileResponse`] containing
70 /// the compiler output, including success/failure status, messages, and possible warnings or errors.
71 /// On failure, returns an [`Error`] describing the issue.
72 ///
73 /// # Errors
74 ///
75 /// Returns an error if the HTTP request fails, if the response cannot be parsed correctly,
76 /// or if the playground service encounters an issue.
77 pub async fn compile(&self, request: &CompileRequest) -> Result<CompileResponse, Error> {
78 self.post(request).await
79 }
80
81 /// Sends a code formatting request to the Rust playground and returns the formatted result.
82 ///
83 /// This asynchronous method takes a [`FormatRequest`] containing the Rust code and formatting options,
84 /// sends it to the Rust playground's format endpoint, and returns the formatted code or any errors.
85 ///
86 /// # Arguments
87 ///
88 /// * `request` - A reference to a [`FormatRequest`] that includes the code to be formatted and
89 /// optional parameters like the edition to use.
90 ///
91 /// # Returns
92 ///
93 /// * `Result<FormatResponse, Error>` - On success, returns a [`FormatResponse`] containing the
94 /// formatted code or an error message if the code could not be formatted.
95 /// On failure, returns an [`Error`] representing issues like network failure or parsing problems.
96 ///
97 /// # Errors
98 ///
99 /// This function may return an error if the request fails, the response is invalid,
100 /// or the Rust playground's formatting service encounters a problem.
101 pub async fn format(&self, request: &FormatRequest) -> Result<FormatResponse, Error> {
102 self.post(request).await
103 }
104
105 /// Sends a Clippy linting request to the Rust playground and returns the analysis result.
106 ///
107 /// This asynchronous method takes a [`ClippyRequest`] containing the Rust code and configuration,
108 /// sends it to the Rust playground's Clippy endpoint, and returns any linter warnings, errors,
109 /// or suggestions provided by Clippy.
110 ///
111 /// # Arguments
112 ///
113 /// * `request` - A reference to a [`ClippyRequest`] that includes the code to be analyzed
114 /// and optional parameters such as edition or crate type.
115 ///
116 /// # Returns
117 ///
118 /// * `Result<ClippyResponse, Error>` - On success, returns a [`ClippyResponse`] containing
119 /// Clippy's diagnostic output (warnings, errors, suggestions). On failure, returns an [`Error`]
120 /// describing what went wrong (e.g., network error, bad request, or service issue).
121 ///
122 /// # Errors
123 ///
124 /// Returns an error if the request cannot be completed, the response is invalid,
125 /// or the Clippy service is unavailable or encounters an internal error.
126 pub async fn clippy(&self, request: &ClippyRequest) -> Result<ClippyResponse, Error> {
127 self.post(request).await
128 }
129
130 /// Sends a Miri request to the Rust playground and returns the result of interpreting the code.
131 ///
132 /// This asynchronous method takes a [`MiriRequest`] containing the Rust code and any
133 /// interpreter-specific options, sends it to the Rust playground's Miri endpoint, and
134 /// returns the result of running the interpreter on the code.
135 ///
136 /// # Arguments
137 ///
138 /// * `request` - A reference to a [`MiriRequest`] that includes the code and metadata
139 /// such as edition, crate type, and other configuration options.
140 ///
141 /// # Returns
142 ///
143 /// * `Result<MiriResponse, Error>` - On success, returns a [`MiriResponse`] containing the
144 /// result of the interpretation. On failure, returns an [`Error`] describing the issue.
145 ///
146 /// # Errors
147 ///
148 /// Returns an error if the request fails, if the response is invalid, or if the Miri service
149 /// encounters an internal issue.
150 pub async fn miri(&self, request: &MiriRequest) -> Result<MiriResponse, Error> {
151 self.post(request).await
152 }
153
154 /// Sends a macro expansion request to the Rust playground and returns the result.
155 ///
156 /// This asynchronous method takes a [`MacroExpansionRequest`] with Rust code containing macros,
157 /// sends it to the Rust playground's macro expansion endpoint, and returns the result
158 /// of the expanded macros.
159 ///
160 /// # Arguments
161 ///
162 /// * `request` - A reference to a [`MacroExpansionRequest`] that includes the code and any
163 /// configuration options like the edition to use.
164 ///
165 /// # Returns
166 ///
167 /// * `Result<MacroExpansionResponse, Error>` - On success, returns a [`MacroExpansionResponse`]
168 /// containing the macro-expanded version of the code. On failure, returns an [`Error`] describing
169 /// the issue.
170 ///
171 /// # Errors
172 ///
173 /// Returns an error if the HTTP request fails, if the response is invalid, or if the macro expansion
174 /// service encounters an issue.
175 pub async fn macro_expansion(
176 &self,
177 request: &MacroExpansionRequest,
178 ) -> Result<MacroExpansionResponse, Error> {
179 self.post(request).await
180 }
181
182 /// Retrieves the list of available crates from the Rust playground.
183 ///
184 /// This asynchronous method sends a GET request to the crates endpoint
185 /// and returns a list of crates supported by the playground environment.
186 ///
187 /// # Returns
188 ///
189 /// * `Result<CratesResponse, Error>` - On success, returns a [`CratesResponse`] containing
190 /// the names and versions of available crates. On failure, returns an [`Error`] describing
191 /// the problem.
192 ///
193 /// # Errors
194 ///
195 /// Returns an error if the request fails, if the response cannot be parsed,
196 /// or if the crates service is unavailable.
197 pub async fn crates(&self) -> Result<CratesResponse, Error> {
198 self.get(Endpoints::Crates).await
199 }
200
201 /// Retrieves the supported versions and metadata of the Rust playground.
202 ///
203 /// This asynchronous method sends a GET request to the versions endpoint and
204 /// returns information about supported Rust versions, targets, and environments.
205 ///
206 /// # Returns
207 ///
208 /// * `Result<VersionsResponse, Error>` - On success, returns a [`VersionsResponse`]
209 /// containing version details. On failure, returns an [`Error`] describing what went wrong.
210 ///
211 /// # Errors
212 ///
213 /// Returns an error if the request cannot be completed, the response is malformed,
214 /// or if the versions service is unavailable.
215 pub async fn versions(&self) -> Result<VersionsResponse, Error> {
216 self.get(Endpoints::Versions).await
217 }
218
219 /// Creates a GitHub Gist from the provided Rust playground code.
220 ///
221 /// This asynchronous method sends a [`GistCreateRequest`] to the Gist creation endpoint
222 /// and returns a response containing the Gist URL or error information.
223 ///
224 /// # Arguments
225 ///
226 /// * `request` - A reference to a [`GistCreateRequest`] that includes the code to be uploaded
227 /// as a Gist and any additional metadata like description or visibility.
228 ///
229 /// # Returns
230 ///
231 /// * `Result<GistResponse, Error>` - On success, returns a [`GistResponse`] containing
232 /// the Gist ID and URL. On failure, returns an [`Error`] describing what went wrong.
233 ///
234 /// # Errors
235 ///
236 /// Returns an error if the HTTP request fails, if the response is malformed,
237 /// or if the Gist service is unavailable.
238 pub async fn gist_create(&self, request: &GistCreateRequest) -> Result<GistResponse, Error> {
239 self.post(request).await
240 }
241
242 /// Retrieves an existing GitHub Gist from the Rust playground.
243 ///
244 /// This asynchronous method sends a GET request to the Gist retrieval endpoint
245 /// using the provided Gist ID and returns the contents of the Gist.
246 ///
247 /// # Arguments
248 ///
249 /// * `id` - A `String` representing the unique identifier of the Gist to retrieve.
250 ///
251 /// # Returns
252 ///
253 /// * `Result<GistResponse, Error>` - On success, returns a [`GistResponse`] containing
254 /// the Gist's code and metadata. On failure, returns an [`Error`] describing the issue.
255 ///
256 /// # Errors
257 ///
258 /// Returns an error if the HTTP request fails, if the response is invalid,
259 /// or if the Gist could not be found.
260 pub async fn gist_get(&self, id: &str) -> Result<GistResponse, Error> {
261 self.get(Endpoints::GistGet(id)).await
262 }
263
264 /// Sends a POST request with a serialized JSON payload to the specified endpoint,
265 /// and deserializes the response into the expected type.
266 ///
267 /// Used internally to interact with Rust playground endpoints.
268 pub async fn post<T, U>(&self, request: &T) -> Result<U, Error>
269 where
270 T: Request,
271 U: Response,
272 {
273 let endpoint = request.endpoint();
274 let url = self.url.join(&endpoint.path())?;
275 let res = self.client.post(url).json(request).send().await?;
276 println!("{:?}", res.headers());
277
278 if !res.status().is_success() {
279 return Err(Error::NoSuccess(res.status().as_u16()));
280 }
281
282 let res = res.json::<U>().await?;
283 Ok(res)
284 }
285
286 /// Sends a GET request to the specified endpoint, and deserializes the response
287 /// into the expected type.
288 ///
289 /// Used internally to interact with Rust playground endpoints.
290 pub async fn get<'a, U>(&self, endpoint: Endpoints<'a>) -> Result<U, Error>
291 where
292 U: Response,
293 {
294 let url = self.url.join(&endpoint.path())?;
295 let res = self.client.get(url).send().await?;
296
297 if !res.status().is_success() {
298 return Err(Error::NoSuccess(res.status().as_u16()));
299 }
300
301 let res = res.json::<U>().await?;
302 Ok(res)
303 }
304}
305
306impl Default for Client {
307 /// Creates a `Client` instance with the following url <https://play.rust-lang.org/>
308 fn default() -> Self {
309 let client = reqwest::Client::new();
310 Self {
311 url: Url::parse("https://play.rust-lang.org/").unwrap(),
312 client,
313 }
314 }
315}
316
317#[cfg(test)]
318mod tests {
319 use super::Client;
320 use crate::endpoints::*;
321
322 #[tokio::test]
323 async fn execute() {
324 let req = ExecuteRequest::default();
325
326 let client = Client::default();
327 let res = client.execute(&req).await;
328
329 println!("{res:?}");
330 assert!(res.is_ok())
331 }
332
333 #[tokio::test]
334 async fn compile() {
335 let req = CompileRequest::default();
336
337 let client = Client::default();
338 let res = client.compile(&req).await;
339
340 println!("{res:?}");
341 assert!(res.is_ok());
342 }
343
344 #[tokio::test]
345 async fn format() {
346 let req = FormatRequest::default();
347
348 let client = Client::default();
349 let res = client.format(&req).await;
350
351 println!("{res:?}");
352 assert!(res.is_ok());
353 }
354
355 #[tokio::test]
356 async fn clippy() {
357 let req = ClippyRequest::default();
358
359 let client = Client::default();
360 let res = client.clippy(&req).await;
361
362 println!("{res:?}");
363 assert!(res.is_ok());
364 }
365
366 #[tokio::test]
367 async fn miri() {
368 let req = MiriRequest::default();
369
370 let client = Client::default();
371 let res = client.miri(&req).await;
372
373 println!("{res:?}");
374 assert!(res.is_ok());
375 }
376
377 #[tokio::test]
378 async fn macro_expansion() {
379 let req = MacroExpansionRequest::default();
380
381 let client = Client::default();
382 let res = client.macro_expansion(&req).await;
383
384 println!("{res:?}");
385 assert!(res.is_ok());
386 }
387
388 #[tokio::test]
389 async fn crates() {
390 let client = Client::default();
391 let res = client.crates().await;
392
393 println!("{res:?}");
394 assert!(res.is_ok());
395 }
396
397 #[tokio::test]
398 async fn version() {
399 let client = Client::default();
400 let res = client.versions().await;
401
402 println!("{res:?}");
403 assert!(res.is_ok());
404 }
405
406 #[tokio::test]
407 #[ignore = "don't flood rust playground gist with useless gists"]
408 async fn gist_create() {
409 let req = GistCreateRequest::new("fn main() { println!(\"Hello, world!\"); }".to_owned());
410
411 let client = Client::default();
412 let res = client.gist_create(&req).await;
413
414 println!("{res:?}");
415 assert!(res.is_ok());
416 }
417
418 #[tokio::test]
419 async fn gist_get() {
420 let id = "ba5e40fb63e78da440797e921bbf2aa6";
421
422 let client = Client::default();
423 let res = client.gist_get(id).await;
424
425 println!("{res:?}");
426 assert!(res.is_ok());
427 }
428}