AWS is divided by regions, where each has its own endpoint to connect to services. Our tool will support two arguments to set a region and endpoint:
.arg(
Arg::with_name("region")
.long("region")
.value_name("REGION")
.help("Sets a region")
.takes_value(true),
)
.arg(
Arg::with_name("endpoint")
.long("endpoint-url")
.value_name("URL")
.help("Sets an endpoint url")
.takes_value(true),
)
We add both to App instance. The tool will support two commands to add a new item and to print all items. The first subcommand is add and it expects three arguments: USER_ID, LONGITUDE, and LATITUDE:
.subcommand(SubCommand::with_name(CMD_ADD).about("add geo record to the table")
.arg(Arg::with_name("USER_ID")
.help("Sets the id of a user")
.required(true)
.index(1))
.arg(Arg::with_name("LATITUDE")
.help("Sets a latitudelongitude of location")
.required(true)
.index(2))
.arg(Arg::with_name("LONGITUDE")
.help("Sets a longitude of location")
.required(true)
.index(3)))
The list subcommand requires USER_ID in arguments only:
.subcommand(SubCommand::with_name(CMD_LIST).about("print all records for the user")
.arg(Arg::with_name("USER_ID")
.help("User if to filter records")
.required(true)
.index(1)))
Add all of the preceding code to the main function. We can use these arguments to create a Region instance that we can use for a connection with DynamoDB:
let region = matches.value_of("endpoint").map(|endpoint| {
Region::Custom {
name: "custom".into(),
endpoint: endpoint.into(),
}
}).ok_or_else(|| format_err!("Region not set"))
.or_else(|_| {
matches.value_of("region")
.unwrap_or("us-east-1")
.parse()
})?;
The code works according to the following logic: if a user sets the --endpoint-url parameter, we create a Region with a custom name and provide an endpoint value. If endpoint is not set, we try to parse the --region parameter to the Region instance, or just use the us-east-1 value by default.
Now, we can use the Region value to create a DynamoDbClient instance:
let client = DynamoDbClient::new(region);
The DynamoDbClient struct is used for sending queries to our DynamoDB instance. We will use this instance in the implementation of our commands. Do you remember the match expression that parses command-line arguments? Add this implementation for the add subcommand first, which puts a new item in a table, as follows:
(CMD_ADD, Some(matches)) => {
let user_id = matches.value_of("USER_ID").unwrap().to_owned();
let timestamp = Utc::now().to_string();
let latitude = matches.value_of("LATITUDE").unwrap().to_owned();
let longitude = matches.value_of("LONGITUDE").unwrap().to_owned();
let location = Location { user_id, timestamp, latitude, longitude };
add_location(&client, location)?;
}
The implementation is simple—we extract all provided arguments, generate a timestamp using the Utc::now call, and convert it into a String type in the ISO-8601 format. Lastly, we fill the Location instance and call the add_location function that we declared before.
We still need to implement the list subcommand:
(CMD_LIST, Some(matches)) => {
let user_id = matches.value_of("USER_ID").unwrap().to_owned();
let locations = list_locations(&client, user_id)?;
for location in locations {
println!("{:?}", location);
}
}
This command extracts USER_ID arguments and calls the list_locations function with the provided user_id value. Finally, we iterate over all locations and print them to the Terminal.
The implementation is finished and we can try it now.