I am using Axum rust to handle Query parameters in the URL within the runtime. The issue that I can't figure out is that the program will only deserialize the first query parameter. If there are two or more, then the second and third query parameters will be read as null.
#![warn(clippy::cargo, clippy::unwrap_used)]
mod config;
mod models;
mod route_handlers;
mod web_server;
use aws_sdk_dynamodb::Client;
use config::{make_config, Opt};
use lambda_runtime::tower::ServiceBuilder;
use std::env::set_var;
use tower_http::add_extension::AddExtensionLayer;
use web_server::get_router;
const REGION: &str = "us-east-1";
#[tokio::main]
async fn main() -> Result<(), lambda_http::Error> {
// AWS Runtime can ignore Stage Name passed from json event
// https://github.com/awslabs/aws-lambda-rust-runtime/issues/782
set_var("AWS_LAMBDA_HTTP_IGNORE_STAGE_IN_PATH", "true");
// required to enable CloudWatch error logging by the runtime
tracing_subscriber::fmt()
.with_max_level(tracing::Level::INFO)
// disable printing the name of the module in every log line.
.with_target(false)
// disabling time is handy because CloudWatch will add the ingestion time.
.without_time()
.init();
// creating a dynamo db client for the router to use
let config = make_config(Opt {
region: Some(REGION.to_owned()),
verbose: true,
})
.await;
let client = std::sync::Arc::new(Client::new(
&config.expect("Could not unwrap aws config for dynamodb client"),
));
let router = get_router();
// adding the client into the router as an extension layer
let router = ServiceBuilder::new()
.layer(AddExtensionLayer::new(client))
.service(router);
lambda_http::run(router).await
}
pub fn get_router() -> axum::Router {
// Sets the axum router to produce only ERROR level logs. The handlers can produce their own level of logs.
const LOG_LEVEL: Level = Level::ERROR;
// With Routing, order does matter, so put the more specific first and then more generic routes later
Router::new()
.route("/conversations", get(list_conversations));
pub async fn list_conversations(
Extension(db_client): Extension<Arc<DynamoDbClient>>,
Query(params): Query<PaginationRequest>,
) -> WebResult {
info!("params: {:?}", params);
let client = db_client.clone();
scan_items::<Conversation>(&client, DBTable::Conversation, params).await
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct PaginationRequest {
// The hash key
pub next_key_a: Option<String>,
// The sort key
pub next_key_b: Option<String>,
// if no pagination should be used for the client, and all data should be returned
pub fetch_all: Option<bool>,
}
When I invoke the api gateway, with next_key_a first, it will be read but next_key_b will be None. If i switch the query parameters, they will then change from Some to None
Calling https://abc.execute-api.us-east-1.amazonaws.com/test-stage/conversations?next_key_b=7e95e561-3b3f-4a82-954f-fb37f8c0f257&next_key_a=053baf43-ac9c-4754-a94a-de982c169204 will cause next_key_b to be Some() and next_key_a to be None.
Then calling https://abc.execute-api.us-east-1.amazonaws.com/test-stage/conversations?next_key_a=7e95e561-3b3f-4a82-954f-fb37f8c0f257&next_key_b=053baf43-ac9c-4754-a94a-de982c169204 will be the opposite.
I think it is something that happens before the payload from API Gateway hits the axum web server because I've tried a bunch of unit tests to make sure this struct can handle this from the url:
#[test]
fn test_pagination_request_full() {
let uri: Uri = "http://example.com/path?next_key_a=hello&next_key_b=world&fetch_all=true"
.parse()
.unwrap();
let result: Query<PaginationRequest> = Query::try_from_uri(&uri).unwrap();
assert_eq!(result.0.next_key_a, Some(String::from("hello")));
assert_eq!(result.0.next_key_b, Some(String::from("world")));
assert_eq!(result.0.fetch_all, Some(true));
}
#[test]
fn test_pagination_request_full_swapped() {
let uri: Uri = "http://example.com/path?next_key_b=world&fetch_all=true&next_key_a=hello"
.parse()
.unwrap();
let result: Query<PaginationRequest> = Query::try_from_uri(&uri).unwrap();
assert_eq!(result.0.next_key_a, Some(String::from("hello")));
assert_eq!(result.0.next_key_b, Some(String::from("world")));
assert_eq!(result.0.fetch_all, Some(true));
}
#[test]
fn test_pagination_request_partial() {
let uri: Uri = "http://example.com/path?next_key_a=hello&fetch_all=true"
.parse()
.unwrap();
let result: Query<PaginationRequest> = Query::try_from_uri(&uri).unwrap();
assert_eq!(result.0.next_key_a, Some(String::from("hello")));
assert_eq!(result.0.next_key_b, None);
assert_eq!(result.0.fetch_all, Some(true));
}
These tests show that Axum can handle the Queries if they are passed into the framework correctly. So I think lambda runtime is working differently than I expected.