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}