RustArena

Step 3 of 7

JSON in, JSON out

JSON APIs send JSON in request bodies and get JSON back. With serde (from the serde module), a handler is three moves: deserialize the body into a request type, check it, serialize the result.

use serde::{Deserialize, Serialize};
use serde_json::json;

#[derive(Deserialize)]
struct CreateUser {
    name: String,
    email: String,
}

#[derive(Serialize)]
struct User {
    id: u32,
    name: String,
    email: String,
}

struct Response {
    status: u16,
    body: String,
}

fn json_response<T: Serialize>(status: u16, value: &T) -> Response {
    Response { status, body: serde_json::to_string(value).expect("response types always serialize") }
}

fn create_user(body: &str, next_id: u32) -> Response {
    let input: CreateUser = match serde_json::from_str(body) {
        Ok(input) => input,
        Err(e) => return json_response(400, &json!({ "error": format!("invalid JSON: {e}") })),
    };
    if !input.email.contains('@') {
        return json_response(422, &json!({ "error": "email must contain @" }));
    }
    let user = User { id: next_id, name: input.name, email: input.email };
    json_response(201, &user)
}

fn main() {
    for body in [r#"{"name": "Ana", "email": "[email protected]"}"#, r#"{"name": "Bo", "email": "bo"}"#, r#"{"name": "Cy"}"#] {
        let r = create_user(body, 7);
        println!("{} {}", r.status, r.body);
    }
}
201 {"id":7,"name":"Ana","email":"[email protected]"}
422 {"error":"email must contain @"}
400 {"error":"invalid JSON: missing field `email` at line 1 column 14"}

Piece by piece:

Common mistakes

Your task

Write create_ticket. The body looks like {"title": "Printer on fire", "priority": "high"}; priority is optional and defaults to normal. Respond with:

Challenge 2

Write the two helpers every handler uses: fn json_response<T: Serialize>(status: u16, value: &T) -> Response (the value serialized as compact JSON) and fn error_response(status: u16, message: &str) -> Response (the body {"error":"<message>"}, built with json! so quotes in the message are escaped).

Challenge 3

PATCH updates only the fields the client sends. Write fn patch_task(task: &mut Task, body: &str) -> Response where the body may contain title and/or done. Answer 400 + {"error":"invalid request body"} if it doesn't parse, 422 + {"error":"title must not be empty"} for a blank title (and then change nothing, not even done), otherwise apply the present fields (trim the title) and answer 200 with the whole task as JSON.

Previous: Routing by method and path Next: Errors become status codes