1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
//! A lightweight crate to interact with the [Exercism website](https://exercism.org)'s APIs.
//!
//! # TOC
//!
//! - [`What is Exercism?`](#what-is-exercism)
//! - [`Installing`](#installing)
//! - [`API clients`](#api-clients)
//! - [`Async methods`](#async-methods)
//! - [`Example`](#example)
//! - [`Credentials`](#credentials)
//! - [`CLI credentials`](#cli-credentials)
//! - [`Custom HTTP client`](#custom-http-client)
//! - [`Crate status`](#crate-status)
//! - [`Minimum Rust version`](#minimum-rust-version)
//!
//! ## What is Exercism?
//!
//! [Exercism](https://exercism.org) is a free, not-for-profit platform to learn new programming
//! languages. It supports a web editor for solving exercises, mentoring with real humans and
//! a lot more. For more information, see [its about page](https://exercism.org/about).
//!
//! ## Installing
//!
//! Add [mini_exercism](crate) to your dependencies:
//!
//! ```toml
//! [dependencies]
//! mini_exercism = "8.0.0"
//! ```
//!
//! or by running:
//!
//! ```bash
//! cargo add mini_exercism
//! ```
//!
//! ## API clients
//!
//! To interact with an [Exercism](https://exericms.org) API, you can simply use one of
//! the provided API clients. Each API has its own client:
//!
//! - [`api::v1::Client`]
//! - [`api::v2::Client`]
//!
//! To create a client, either use its `new` method to create a default instance, or use its
//! `builder` method to construct one:
//!
//! ```no_run
//! use mini_exercism::api;
//!
//! fn get_default_client() -> anyhow::Result<api::v2::Client> {
//! Ok(api::v2::Client::new()?)
//! }
//!
//! fn get_custom_client() -> anyhow::Result<api::v2::Client> {
//! let mut builder = api::v2::Client::builder();
//! // ... customize API client with builder ...
//!
//! Ok(builder.build()?)
//! }
//! ```
//!
//! ## Async methods
//!
//! The client methods used to query the APIs for information are `async`. As such, in order to
//! call them, you will need to use the `await` keyword. For more information on async programming
//! in Rust, see [Asynchronous Programming in Rust](https://rust-lang.github.io/async-book/).
//!
//! Asynchronous programming in Rust requires an _async runtime_. Runtimes handle the execution
//! of asynchronous code and the callbacks.
//!
//! One popular async runtime is [Tokio](https://tokio.rs/). It offers a simple way to write
//! programs that support asynchronous code. You can use the [`#[tokio::main]`](https://docs.rs/tokio/latest/tokio/attr.main.html)
//! attribute to make your `main` function `async`, allowing the use of `await`:
//!
//! ```no_run
//! use mini_exercism::api;
//!
//! #[tokio::main]
//! async fn main() -> anyhow::Result<()> {
//! let client = api::v2::Client::new()?;
//! let tracks = client.get_tracks(None).await?;
//! // ...
//!
//! Ok(())
//! }
//! ```
//!
//! [Tokio](https://tokio.rs/) offers many customization options; see the [docs](https://docs.rs/tokio/latest/tokio/index.html)
//! for more details.
//!
//! ## Example
//!
//! ```no_run
//! use mini_exercism::api;
//!
//! async fn print_language_tracks() -> anyhow::Result<()> {
//! let client = api::v2::Client::new()?;
//!
//! let tracks = client.get_tracks(None).await?.tracks;
//! for track in &tracks {
//! println!("Exercism language track: {}", track.title);
//! }
//!
//! Ok(())
//! }
//!
//! async fn print_solutions(track: &str) -> anyhow::Result<()> {
//! let client = api::v2::Client::new()?;
//!
//! let solutions = client.get_exercises(track, None).await?.solutions;
//! for solution in &solutions {
//! println!(
//! "Solution for exercise {}, public URL: {}",
//! solution.exercise.title, solution.public_url,
//! );
//! }
//!
//! Ok(())
//! }
//! ```
//!
//! ## Credentials
//!
//! API clients use [`Credentials`] to perform requests as a specific user. Some requests can
//! work both anonymously and authenticated (behaving differently depending on which is used),
//! others require authentication to work.
//!
//! [`Credentials`] use [Exercism](https://exercism.org) API tokens to identify a user. This is the
//! token that is used for the [Exercism CLI application](https://exercism.org/docs/using/solving-exercises/working-locally).
//! It can be fetched from the [Exercism's user settings page](https://exercism.org/settings/api_cli).
//!
//! To pass [`Credentials`] to an API client, use its `builder`:
//!
//! ```no_run
//! use mini_exercism::api;
//! use mini_exercism::core::Credentials;
//!
//! fn get_api_client() -> anyhow::Result<api::v2::Client> {
//! let credentials = Credentials::from_api_token("SOME_API_TOKEN");
//! Ok(api::v2::Client::builder()
//! .credentials(credentials)
//! .build()?)
//! }
//! ```
//!
//! ## CLI credentials
//!
//! This crate provides a helper function to fetch the [`Credentials`] used by the currently-installed
//! [Exercism CLI application](https://exercism.org/docs/using/solving-exercises/working-locally).
//! In order to use this, you need to enable the `cli` feature:
//!
//! ```toml
//! [dependencies]
//! mini_exercism = { version = "8.0.0", features = ["cli"] }
//! ```
//!
//! Then, you can fetch CLI credentials and use them to perform API requests. Note that it's
//! possible for the method to fail to find credentials if the CLI is not installed, for instance.
//!
//! ```no_run
//! use mini_exercism::api;
//!
//! fn get_api_client() -> anyhow::Result<api::v2::Client> {
//! let mut client_builder = api::v2::Client::builder();
//!
//! #[cfg(feature = "cli")]
//! if let Ok(credentials) = mini_exercism::cli::get_cli_credentials() {
//! client_builder.credentials(credentials);
//! } else {
//! // Find some other way to fetch credentials, or perform queries anonymously
//! }
//!
//! Ok(client_builder.build()?)
//! }
//! ```
//!
//! ## Custom HTTP client
//!
//! Internally, [mini_exercism](crate) uses the [reqwest](https://crates.io/crates/reqwest)
//! library to perform HTTP calls (whose types are re-exported through the [`mini_exercism::http`](http)
//! module). Unless overridden, API clients will create a default HTTP client. If you need to customize the
//! behaviour of the HTTP client, you can use the API client's `builder` to specify a different HTTP client:
//!
//! ```no_run
//! use mini_exercism::api;
//! use mini_exercism::http;
//!
//! fn get_api_client() -> anyhow::Result<api::v2::Client> {
//! let http_client_builder = http::Client::builder();
//! // ... customize HTTP client with `http_client_builder` here ...
//! let http_client = http_client_builder.build()?;
//!
//! Ok(api::v2::Client::builder()
//! .http_client(http_client)
//! .build()?)
//! }
//!
//! // Another possible way:
//! fn get_api_client_too() -> anyhow::Result<api::v2::Client> {
//! Ok(api::v2::Client::builder()
//! .build_http_client(|builder| {
//! // ... customize HTTP client with `builder` here ...
//! builder
//! })
//! .build()?)
//! }
//! ```
//!
//! ## Retry support
//!
//! Recently (circa 2025), the Exercism API started throttling incoming requests much more
//! aggressively to fight a bot situation. Because of this, it is now sometimes necessary to
//! retry API requests should they be throttled.
//!
//! By default, API clients will retry API requests up to 5 times (with exponential backoff)
//! before giving up. To configure this, you can either use the API client's `builder`'s
//! `num_retries` or `retry_policy` methods:
//!
//! ```no_run
//! use std::time::Duration;
//!
//! use mini_exercism::api;
//! use mini_exercism::http::retry::Jitter;
//! use mini_exercism::http::retry::policies::ExponentialBackoff;
//!
//! fn get_api_client() -> anyhow::Result<api::v2::Client> {
//! Ok(api::v2::Client::builder().num_retries(2).build()?)
//! }
//!
//! // Or fully customize the retry policy:
//! fn get_api_client_too() -> anyhow::Result<api::v2::Client> {
//! let retry_policy = ExponentialBackoff::builder()
//! .retry_bounds(Duration::from_secs(1), Duration::from_secs(30))
//! .jitter(Jitter::Bounded)
//! .build_with_max_retries(2);
//!
//! Ok(api::v2::Client::builder()
//! .retry_policy(retry_policy)
//! .build()?)
//! }
//! ```
//!
//! ## Crate status
//!
//! Currently, this crate is a bit minimalistic and does not implement all the [Exercism](https://exercism.org)
//! API endpoints. To suggest new endpoints to add, you can enter an [issue](https://github.com/clechasseur/mini_exercism/issues).
//! Or, even better, don't hesitate to submit a [pull request](https://github.com/clechasseur/mini_exercism/pulls)! 😁
//!
//! ## Minimum Rust version
//!
//! [mini_exercism](crate) currently builds on Rust 1.85 or newer.
//!
//! [`Credentials`]: core::Credentials
// Re-export `reqwest`, `reqwest-middleware` and `reqwest-retry` in a `http` module
// Re-export `Bytes` and some `futures` types in a `stream` module
pub use crateError;
pub use crateResult;