playground_api/
blocking.rs

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