Accept and validate JSON

Starting point: the app from the Quickstart.

Goal: accept POST /notes, return the note as JSON, and reject blank text. This step does not store notes yet.

  1. 01callerJSON body
  2. 02frameworkParseValidatedJson reads the body
  3. 03youValidateYour rule runs before the handler
  4. 04youHandlerRuns only after successful validation
  5. 05callerJSON response

1. Add the input dependencies

Add these two lines under [dependencies] in your existing examples/my-first-app/Cargo.toml. Keep the other dependencies:

toml
iet-core = { path = "../../crates/core/iet-core" }
serde = { version = "1", features = ["derive"] }

serde converts between JSON and Rust values. iet-core provides the validation trait and application error type; it does not start a server or connect a database.

2. Replace main.rs

This is the complete file. It keeps /hello and adds /notes.

examples/my-first-app/src/main.rsrust
use axum::{Json, routing::{get, post}};
use iet_core::{AppError, AppResult, ErrorKind, Validate};
use iet_http_axum::{HostConfig, HttpHost, RestApi, ValidatedJson};
use serde::{Deserialize, Serialize};

#[derive(Deserialize, Serialize)]
struct CreateNote {
    text: String,
}

impl Validate for CreateNote {
    fn validate(&self) -> AppResult<()> {
        if self.text.trim().is_empty() {
            return Err(AppError::new(
                ErrorKind::Validation,
                "note.text_required",
                "A note needs some text",
            ));
        }
        Ok(())
    }
}

async fn create_note(
    ValidatedJson(input): ValidatedJson<CreateNote>,
) -> Json<CreateNote> {
    Json(input)
}

async fn hello() -> &'static str {
    "Hello from my application"
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    let router = RestApi::new()
        .route("/hello", get(hello))
        .route("/notes", post(create_note))
        .finish(());

    let config = HostConfig {
        address: "127.0.0.1:3012".parse()?,
        ..HostConfig::default()
    };
    let host = HttpHost::bind(config, router).await?;
    println!("Listening on http://127.0.0.1:3012");
    host.run(async {
        let _ = tokio::signal::ctrl_c().await;
    })
    .await?;
    Ok(())
}

3. Try both outcomes

Stop the previous process with Ctrl+C, then rerun:

sh
cargo run --manifest-path examples/my-first-app/Cargo.toml
Requestsh
curl -i http://127.0.0.1:3012/notes \
  -H 'content-type: application/json' \
  -d '{"text":"buy milk"}'
Response (selected headers)
HTTP/1.1 200 OK
content-type: application/json

{"text":"buy milk"}

Your handler ran and echoed the input. 200, rather than 201, is intentional: this step has not created a stored resource.

Requestsh
curl -i http://127.0.0.1:3012/notes \
  -H 'content-type: application/json' \
  -d '{"text":"   "}'
Response (selected headers)
HTTP/1.1 422 Unprocessable Entity
content-type: application/problem+json

{"type":"about:blank","title":"Validation failed","status":422,"code":"note.text_required","errors":[]}

Your handler did not run. ValidatedJson called your rule and converted the validation error to the framework's HTTP problem response.

What did the framework actually add?

Your responsibility

Define CreateNote and decide what is valid. The framework does not know that blank note text is a problem until you write Validate.

Framework responsibility

ValidatedJson<CreateNote> parses the body, runs validate(), and rejects invalid input before the handler. You do not repeat that HTTP plumbing in every handler.

Try changing the rule: reject text longer than 200 characters. Restart and send a longer note. The response should be 422 with the error code you choose.

Validation outside HTTP

If you later move the operation into a service, validate there too. A worker or a test may call that service without using an HTTP extractor. Reuse the same rule; do not assume every caller came through ValidatedJson.

Next: keep the note

You have a running endpoint, an input type, and a validation rule. Now learn shared state and service boundaries in Store and retrieve notes.

IET Framework documentation — built with the IET Framework.