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