Rust
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Ownership and Borrowing
-
Section 4: Structuring Data
-
Section 5: Collections and Error Handling
-
Section 6: Traits and Generics
-
Section 7: Concurrency
-
Section 8: Building for the Web
-
Section 9: Memory and Performance
-
Section 10: More Standard Library and Ecosystem
-
Section 11: Advanced Rust
-
Section 12: Rust for Systems and WebAssembly
-
Section 13: Tooling and Best Practices
-
Section 14: Data Structures and Algorithms in Rust
-
Section 15: Practical Projects
-
Section 16: Interview Practice
-
Section 17: std::collections In Depth
-
Section 18: std::io and std::fs In Depth
-
Section 19: std::net
-
Section 20: std::option and std::result In Depth
-
Section 21: std::iter In Depth
-
Section 22: std::sync In Depth
-
Section 23: std::string and std::str
-
Section 24: Cargo and Crates.io Ecosystem
-
Section 25: Popular Crates Ecosystem
-
Section 26: Rust Memory Model Deep Dive
-
Section 27: More Practice Exercises
-
Section 28: More Interview Practice
-
Section 29: Async Rust Deep Dive
-
Section 30: Tokio Ecosystem In Depth
-
Section 31: Error Handling Ecosystem Deep Dive
-
Section 32: Serde In Depth
-
Section 33: Web Development with Rust Deep Dive
-
Section 34: Database Access Ecosystem
-
Section 35: Rust for Embedded Systems Deep Dive
-
Section 36: Rust Macros In Depth
-
Section 37: Advanced Trait System
-
Section 38: Unsafe Rust In Depth
-
Section 39: Rust CLI Development
-
Section 40: Testing Ecosystem Deep Dive
-
Section 41: WebAssembly Deep Dive
-
Section 42: Rust Design Patterns
-
Section 43: More Data Structures in Rust
-
Section 44: Final Practice Projects
-
Section 45: Rust Performance Optimization
-
Section 46: Rust Ecosystem Tooling
-
Section 47: More Interview and Review
49: Handling JSON and Routing
I've seen this exact scenario play out in countless code reviews. You've set up your Axum or Actix routes, you've defined your data structures, and you're sending a perfectly valid JSON object from your frontend. But for some reason, your server keeps screaming 400 Bad Request, and you're staring at your code wondering where the typo is.
use axum::{routing::post, Json, Router};
use serde::Deserialize;
#[derive(Deserialize)]
struct CreateUser {
username: String,
email_address: String,
}
async fn create_user(Json(payload): Json<CreateUser>) -> String {
format!("User {} created!", payload.username)
}
// ... router setup ...
The Mystery of the 400 Bad Request
On the surface, that code looks bulletproof. But here is the catch: most frontend frameworks (React, Vue, etc.) and JavaScript APIs default to camelCase. Your frontend is likely sending {"username": "rustacean", "emailAddress": "rust@example.com"}.
Rust, however, follows snake_case. Serde, the engine doing the heavy lifting here, is strict. It looks for a field called email_address, doesn't find it because the JSON has emailAddress, and decides the request is malformed. It doesn't guess; it just fails. If you aren't logging the specific deserialization error, you're left guessing why a "valid" JSON object is being rejected.
Bridging the Gap with Serde Attributes
You could manually rename every single field using #[serde(rename = "emailAddress")], but that's a tedious waste of your time and makes your structs look cluttered. Instead, use the container attribute to tell Serde to translate the casing for the entire struct.
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")] // This is the magic line
struct CreateUser {
username: String,
email_address: String,
}
Now, Serde knows that when it sees emailAddress in the JSON, it should map it to email_address in your Rust struct. I always recommend adding this by default if you're building a public-facing API; it saves you from a dozen annoying bugs down the road.
Connecting Dynamic Routes to JSON Payloads
Now that we can actually parse the data, let's talk about routing. In a real app, you're rarely just hitting a flat /create-user endpoint. You're usually dealing with resources, like /users/:id/update.
The trick here is combining Path extractors with Json extractors. Order matters in some frameworks, but in Axum, the general rule is that you can have multiple extractors, but only one "body" extractor (like Json), and it must come last. If you try to put Path after Json, you'll find the compiler complaining or the request hanging.
use axum::{extract::{Path, Json}, routing::patch, Router};
use serde::Deserialize;
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct UpdateUser {
display_name: Option<String>,
bio: Option<String>,
}
async fn update_user_profile(
Path(user_id): Path<u64>,
Json(payload): Json<UpdateUser>
) -> String {
format!("Updating user {} with name {:?}", user_id, payload.display_name)
}
// In your main function:
// .route("/users/:id/update", patch(update_user_profile))
Notice I used Option<String> for the update fields. This is a crucial pattern for JSON APIs. By using Option, you allow the client to send a partial update. If they only want to change the bio, they can omit displayName, and Serde will simply set that field to None rather than failing the entire request.
📋 Practical Task
Build a Product Inventory Management Endpoint
Your goal is to create a specialized route for an inventory system. You need to implement a PUT handler that updates a specific product's details.
- The Route:
/inventory/:product_id - The Payload: The frontend sends JSON in
camelCasewith the fieldsproductName(String) andstockQuantity(u32). - The Requirements:
- Define a
ProductUpdatestruct that correctly maps thecamelCaseJSON tosnake_caseRust fields. - Create a handler function that extracts both the
product_idfrom the URL path and theProductUpdatedata from the JSON body. - The handler should return a string confirming the update, including both the ID and the new product name.
- Define a
There are no comments for now.