Skip to content

Commit 2ee2bba

Browse files
Desktop app (#21)
* overview * course modules * remove sqlite and diesel * reorder * added: lessons 01,02,03,04, and exercises tab for the rust chapter * ownership * added: new exercise, lessons 07,08,09,10,11,12 * added: on data, lesson 01, 02, on networking 01, 02, 05 * added project ideas * add slint course * public apis --------- Co-authored-by: robertDinca2003 <[email protected]>
1 parent 29d713b commit 2ee2bba

File tree

2 files changed

+241
-0
lines changed

2 files changed

+241
-0
lines changed
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
# Fetching Public APIs in Rust
2+
3+
This guide shows how to fetch public APIs in Rust using `reqwest` and `tokio`.
4+
We’ll walk through small examples and explain what’s happening.
5+
6+
---
7+
8+
## 1. Setup
9+
10+
In `Cargo.toml`:
11+
12+
```toml
13+
[dependencies]
14+
tokio = { version = "1", features = ["full"] }
15+
reqwest = { version = "0.11", features = ["json"] }
16+
serde = { version = "1.0", features = ["derive"] }
17+
serde_json = "1.0"
18+
```
19+
20+
**Explanation:**
21+
- **tokio**: Async runtime that allows us to write asynchronous code (`async`/`await`).
22+
- **reqwest**: HTTP client library for making GET/POST requests.
23+
- **serde** and **serde_json**: For converting JSON data into Rust structs automatically.
24+
25+
---
26+
27+
## 2. Fetch a Joke
28+
29+
```rust
30+
use reqwest::Client;
31+
use serde::Deserialize;
32+
33+
#[derive(Debug, Deserialize)]
34+
struct Joke { setup: String, punchline: String }
35+
36+
#[tokio::main]
37+
async fn main() -> Result<(), Box<dyn std::error::Error>> {
38+
let client = Client::new();
39+
let url = "https://official-joke-api.appspot.com/random_joke";
40+
41+
let joke: Joke = client.get(url).send().await?.json().await?;
42+
43+
println!("{} — {}", joke.setup, joke.punchline);
44+
Ok(())
45+
}
46+
```
47+
48+
**Explanation:**
49+
- We create a `Client` to make HTTP requests.
50+
- `client.get(url)` creates a GET request.
51+
- `.send().await?` sends the request and waits for the response.
52+
- `.json().await?` converts the JSON into our `Joke` struct automatically using `serde`.
53+
- We then print the joke’s setup and punchline.
54+
55+
---
56+
57+
## 3. Weather API Example (OpenWeatherMap)
58+
59+
You can sign up for a free API key at [https://openweathermap.org/](https://openweathermap.org/).
60+
61+
```rust
62+
use reqwest::Client;
63+
use serde::Deserialize;
64+
65+
#[derive(Debug, Deserialize)]
66+
struct WeatherResponse {
67+
name: String,
68+
main: Main,
69+
weather: Vec<Weather>,
70+
}
71+
72+
#[derive(Debug, Deserialize)]
73+
struct Main { temp: f64 }
74+
75+
#[derive(Debug, Deserialize)]
76+
struct Weather { description: String }
77+
78+
#[tokio::main]
79+
async fn main() -> Result<(), Box<dyn std::error::Error>> {
80+
let api_key = "YOUR_API_KEY"; // from OpenWeather
81+
let city = "London";
82+
let url = format!(
83+
"https://api.openweathermap.org/data/2.5/weather?q={}&appid={}&units=metric",
84+
city, api_key
85+
);
86+
87+
let client = Client::new();
88+
let resp: WeatherResponse = client.get(&url).send().await?.json().await?;
89+
90+
println!("Weather in {}: {}°C, {}",
91+
resp.name,
92+
resp.main.temp,
93+
resp.weather.first().map(|w| &w.description).unwrap_or(&"No data".to_string())
94+
);
95+
96+
Ok(())
97+
}
98+
```
99+
100+
**Explanation:**
101+
- The `WeatherResponse` struct matches the JSON structure returned by OpenWeather.
102+
- We use `format!` to insert the city name and API key into the URL.
103+
- The `units=metric` parameter tells the API to return Celsius temperatures.
104+
- After parsing the JSON, we display the city name, temperature, and weather description.
105+
- Always store your API key in an environment variable in real apps for security.
106+
107+
---
108+
109+
## 4. Fetch a Random Cat Fact
110+
111+
```rust
112+
use reqwest::Client;
113+
use serde::Deserialize;
114+
115+
#[derive(Debug, Deserialize)]
116+
struct CatFact { fact: String }
117+
118+
#[tokio::main]
119+
async fn main() -> Result<(), Box<dyn std::error::Error>> {
120+
let client = Client::new();
121+
let url = "https://catfact.ninja/fact";
122+
123+
let fact: CatFact = client.get(url).send().await?.json().await?;
124+
println!("Cat fact: {}", fact.fact);
125+
Ok(())
126+
}
127+
```
128+
129+
**Explanation:**
130+
- This example is almost identical to the joke example but uses another API.
131+
- The API returns JSON with a single field, `fact`.
132+
- This is perfect for learning because you can easily change the URL and struct to fetch other simple APIs.
133+
134+
---
135+
136+
## General Notes
137+
138+
- **Error handling**: In production, always check if the request succeeded before parsing. Use `.error_for_status()` to catch HTTP errors.
139+
- **Performance**: Reuse the same `Client` across requests instead of creating a new one each time.
140+
- **Security**: Never hardcode API keys in your source code. Use environment variables or secure config files.
141+
- **Data mapping**: Struct field names must match the JSON keys or use `#[serde(rename = "json_key_name")]`.
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
---
2+
title: Network Error Handling
3+
---
4+
5+
---
6+
7+
Network operations are inherently unreliable; connections can fail, servers can be offline, and data can be malformed. In Rust, the **`reqwest`** crate handles these possibilities by returning a `Result` for every operation, allowing you to build robust applications that can gracefully recover from failures. This lesson will show you how to handle these errors, from simple propagation to detailed analysis.
8+
9+
---
10+
11+
### 1\. The `reqwest::Error` Type
12+
13+
All errors from `reqwest` are consolidated into a single, comprehensive **`reqwest::Error`** enum. This error type provides detailed information about what went wrong, including connection issues, HTTP status codes, and serialization failures. You don't need to import `reqwest::Error` explicitly; it's the type returned by `reqwest`'s functions, and the `?` operator works on it automatically.
14+
15+
---
16+
17+
### 2\. Simple Error Handling with `?`
18+
19+
The simplest and most common way to handle a `reqwest` error is with the **`?`** operator. This works because most `reqwest` functions return a `Result<T, reqwest::Error>`. The `?` operator will propagate any error back to the caller, making your code concise.
20+
21+
```rust
22+
use reqwest;
23+
use tokio;
24+
25+
#[tokio::main]
26+
async fn main() -> Result<(), reqwest::Error> {
27+
// This will work because main() returns a Result<(), reqwest::Error>
28+
// The '?' operator will propagate any error from the get() call.
29+
let response = reqwest::get("https://httpbin.org/get").await?;
30+
31+
// The '?' will also propagate an error if the response body cannot be read.
32+
let body = response.text().await?;
33+
34+
println!("Response body:\n{}", body);
35+
36+
Ok(())
37+
}
38+
```
39+
40+
This is perfect for small applications or for when you simply want to log an error and exit.
41+
42+
---
43+
44+
### 3\. Granular Error Handling with `match`
45+
46+
For more advanced applications, you may want to handle different types of errors differently. For example, a network timeout might be retried, while a "404 Not Found" error might be displayed to the user. You can use a `match` expression or methods on the `reqwest::Error` type to get this level of detail.
47+
48+
The `reqwest::Error` has several useful methods to check for specific error categories:
49+
50+
- **`is_connect()`**: Returns true if the error was a connection issue (e.g., DNS failure, connection refused).
51+
- **`is_timeout()`**: Returns true if the operation timed out.
52+
- **`status()`**: Returns the `StatusCode` for non-successful HTTP responses. This is a `Option<StatusCode>`, so you must handle the `Some` and `None` cases.
53+
- **`is_client_error()`**: Returns true if the status code is a `4xx`.
54+
- **`is_server_error()`**: Returns true if the status code is a `5xx`.
55+
- **`is_decode()`**: Returns true if deserializing the response body (e.g., JSON) failed.
56+
57+
<!-- end list -->
58+
59+
```rust
60+
use reqwest::{self, StatusCode};
61+
use tokio;
62+
63+
#[tokio::main]
64+
async fn main() {
65+
let response = reqwest::get("https://httpbin.org/status/404").await;
66+
67+
match response {
68+
Ok(res) => {
69+
// Check if the status code indicates a success (200-299)
70+
if res.status().is_success() {
71+
println!("Request was successful!");
72+
} else {
73+
// If the status is not a success, you can handle it here.
74+
println!("HTTP Error: {}", res.status());
75+
76+
// You can also match on the status code itself.
77+
match res.status() {
78+
StatusCode::NOT_FOUND => println!("The resource was not found (404)"),
79+
StatusCode::UNAUTHORIZED => println!("Authentication failed (401)"),
80+
_ => println!("An unexpected HTTP error occurred."),
81+
}
82+
}
83+
},
84+
Err(e) => {
85+
// Check for different kinds of network errors.
86+
if e.is_connect() {
87+
println!("A connection error occurred. Is the server running?");
88+
} else if e.is_timeout() {
89+
println!("The request timed out. Please check your network.");
90+
} else if e.is_decode() {
91+
println!("Failed to decode the response body. Is the format correct?");
92+
} else {
93+
println!("An unexpected request error occurred: {:?}", e);
94+
}
95+
},
96+
}
97+
}
98+
```
99+
100+
This granular approach gives you full control, allowing you to implement specific logic for each potential failure point.

0 commit comments

Comments
 (0)