diff --git a/Cargo.toml b/Cargo.toml index c17b4799..0319017d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ members = [ "hello-world", "http-proxy", "json", + "json_decode_error", "json_error", "jsonrpc", "juniper", diff --git a/json_decode_error/Cargo.toml b/json_decode_error/Cargo.toml new file mode 100644 index 00000000..86438444 --- /dev/null +++ b/json_decode_error/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "json_decode_error" +version = "0.1.0" +authors = ["Stig Johan Berggren "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +actix-web = "2.0.0" +actix-rt = "1.0.0" +serde = "1.0.104" diff --git a/json_decode_error/README.md b/json_decode_error/README.md new file mode 100644 index 00000000..e2b5c26a --- /dev/null +++ b/json_decode_error/README.md @@ -0,0 +1,75 @@ +# JSON decode errors + +This example demonstrates how to return useful error messages to the client +when the server receives a request with invalid JSON, or which cannot be +deserialized to the expected model. By configuring an `error_handler` on the +route, we can set appropriate response codes and return the string +representation of the error. + +## Usage + +```shell +cd examples/json_decode_error +cargo run +# Started HTTP server: 127.0.0.1:8088 +``` + +## Examples + +The examples use `curl -i` in order to show the status line with the response +code. The response headers have been omitted for brevity, and replaced with an +ellipsis `...`. + +- A well-formed request + + ```shell + $ curl -i 127.0.0.1:8088 -H 'Content-Type: application/json' -d '{"name": "Alice"}' + HTTP/1.1 200 OK + ... + + Hello Alice! + ``` + +- Missing `Content-Type` header + + ```shell + $ curl -i 127.0.0.1:8088 -d '{"name": "Bob"}' + HTTP/1.1 415 Unsupported Media Type + ... + + Content type error + ``` + +- Malformed JSON + + ```shell + $ curl -i 127.0.0.1:8088 -H 'Content-Type: application/json' -d '{"name": "Eve}' + HTTP/1.1 400 Bad Request + ... + + Json deserialize error: EOF while parsing a string at line 1 column 14 + ``` + +- JSON value of wrong type + + ```shell + $ curl -i 127.0.0.1:8088 -H 'Content-Type: application/json' -d '{"name": 350}' + HTTP/1.1 422 Unprocessable Entity + ... + + Json deserialize error: invalid type: integer `350`, expected a string at line 1 column 12 + ``` + +- Wrong JSON key + + ```shell + $ curl -i 127.0.0.1:8088 -H 'Content-Type: application/json' -d '{"namn": "John"}' + HTTP/1.1 422 Unprocessable Entity + ... + + Json deserialize error: missing field `name` at line 1 column 16 + ``` + +## More documentation + +[`actix_web::web::JsonConfig`](https://docs.rs/actix-web/latest/actix_web/web/struct.JsonConfig.html) diff --git a/json_decode_error/src/main.rs b/json_decode_error/src/main.rs new file mode 100644 index 00000000..f534625d --- /dev/null +++ b/json_decode_error/src/main.rs @@ -0,0 +1,44 @@ +use actix_web::{ + error, post, web, App, FromRequest, HttpRequest, HttpResponse, HttpServer, Responder, +}; +use serde::Deserialize; + +#[derive(Deserialize)] +struct Info { + name: String, +} + +#[post("/")] +async fn greet(name: web::Json) -> impl Responder { + HttpResponse::Ok().body(format!("Hello {}!", name.name)) +} + +fn json_error_handler(err: error::JsonPayloadError, _req: &HttpRequest) -> error::Error { + use actix_web::error::JsonPayloadError; + + let detail = err.to_string(); + let resp = match &err { + JsonPayloadError::ContentType => { + HttpResponse::UnsupportedMediaType().body(detail) + } + JsonPayloadError::Deserialize(json_err) if json_err.is_data() => { + HttpResponse::UnprocessableEntity().body(detail) + } + _ => HttpResponse::BadRequest().body(detail), + }; + error::InternalError::from_response(err, resp).into() +} + +#[actix_rt::main] +async fn main() -> std::io::Result<()> { + HttpServer::new(|| { + App::new() + .service(greet) + .app_data(web::Json::::configure(|cfg| { + cfg.error_handler(json_error_handler) + })) + }) + .bind("127.0.0.1:8088")? + .run() + .await +}