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}