We have a few code paths that some url paths can run when we receive invalid input. We'll write one new test: handles_empty_pokemon
to handle the 400
case when we receive an empty pokemon.
Receiving an empty string as the request to retrieve a pokemon is a problem because we know there will be no sqlx row returned for this request and it was likely user error when constructing the request.
Our test will be very similar to the first test we created with two changes:
- We'll use a new fixture called
empty-pokemon-request.json
- The JSON response will be
{"error": "searched for empty pokemon"}
.
#[tokio::test]
async fn handles_empty_pokemon() {
let input =
include_str!("empty-pokemon-request.json");
let request = lambda_http::request::from_str(input)
.expect("failed to create request");
let response = function_handler(request)
.await
.expect("failed to handle request");
assert_eq!(
response.body(),
&Body::Text(
"{\"error\":\"searched for empty pokemon\"}"
.to_string()
)
);
}
Setting up a new fixture
The empty-pokemon-request.json
fixture is going to be a copy of the apigw-request.json
fixture.
The only difference is going to be right at the top. path
will become "//"
. This will ensure that our request sends an empty string as the pokemon name.
{
"resource": "/{proxy+}",
"path": "//",
"httpMethod": "POST",
...
}
Responding with JSON
Back up in our match
, we'll change the code block associated with the Some("")
branch to return a 400
status code and a JSON error response.
Some("") => {
let error_message =
serde_json::to_string(&json!({
"error": "searched for empty pokemon"
}))?;
let resp = Response::builder()
.status(400)
.header(CONTENT_TYPE, "application/json")
.body(Body::Text(error_message))?;
Ok(resp)
}
Bring json
into scope as well.
use serde_json::json;
The json!
macro comes from serde_json
and allows us to construct arbitrary JSON in a way that is familiar to anyone that has written JSON before.
The json!
macro is a function call with regular JSON as an input. This will build up a serde_json::Value
from our input.
The Value
enum is the Rust representation of arbitrary JSON. We can pass this into the serde_json::to_string
function to serialize the Value
to a JSON string. This string can then be used directly as our response value.
serde_json::to_string
can fail, but in our usage it won't. You can read up on how the serde_json::to_string
function can fail in the docs.
Passing tests
Our tests should now pass.
❯ DATABASE_URL=mysql://127.0.0.1:3306 cargo test -p pokemon-api
Compiling pokemon-api v0.1.0 (/rust-adventure/pokemon-api-netlify/crates/pokemon-api)
Finished test [unoptimized + debuginfo] target(s) in 2.01s
Running unittests src/main.rs (target/debug/deps/pokemon_api-b905b4a344b0a9e0)
running 2 tests
test tests::handles_empty_pokemon ... ok
test tests::accepts_apigw_request ... ok
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.75s
and we can also use our new fixture when we invoke
.
cargo lambda invoke pokemon-api --data-file ./crates/pokemon-api/src/empty-pokemon-request.json
{
"statusCode": 400,
"headers":
{
"content-type": "application/json"
},
"multiValueHeaders":
{
"content-type":
[
"application/json"
]
},
"body": "{\"error\":\"searched for empty pokemon\"}",
"isBase64Encoded": false
}
You can also build and deploy again, although due to the way Netlify's pathing works, you won't be able to get anything other than a 400 out of the result. We'll build a rewrite rule out in the next lesson.
DATABASE_URL=mysql://127.0.0.1:3306 cargo lambda build -p pokemon-api
cp target/lambda/pokemon-api/bootstrap functions/pokemon-api
netlify deploy --prod
curl https://<site-url>.netlify.app/.netlify/functions/pokemon-api/
{"error":"searched for empty pokemon"}