Do It All In Rust #8

Open
djsweet wants to merge 4 commits from djsweet/get-it-started-in-rust into main
4 changed files with 1796 additions and 0 deletions
Showing only changes of commit 689116efac - Show all commits

12
.gitignore vendored
View file

@ -1,3 +1,15 @@
.env
.envrc
.DS_Store
# Added by cargo
/target
# Added by cargo
#
# already existing elements were commented out
#/target

1604
Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

14
Cargo.toml Normal file
View file

@ -0,0 +1,14 @@
[package]
name = "smsbb"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
aws-config = { version = "1.1.5", features = ["sso"] }
aws-sdk-dynamodb = "1.14.0"
aws-sdk-sns = "1.13.0"
aws-sdk-sqs = "1.13.0"
clap = "4.4.18"
tokio = { version = "1.36.0", features = ["full"] }

166
src/main.rs Normal file
View file

@ -0,0 +1,166 @@
use aws_config::meta::region::RegionProviderChain;
use aws_config::BehaviorVersion;
use aws_sdk_dynamodb::operation::create_table::CreateTableError;
// use aws_sdk_sns::{Client as SnsClient, Error as SnsError};
use aws_sdk_dynamodb::Client as DynamoClient;
use aws_sdk_dynamodb::types::{AttributeDefinition, BillingMode, KeySchemaElement, KeyType, ScalarAttributeType};
// const TOPIC_PREFIX: &'static str = "smsbb_";
const TABLE_PREFIX: &'static str = "smsbb_";
/*
fn full_topic_name(s: &str) -> String {
format!("{}{}", TOPIC_PREFIX, s)
}
*/
fn table_name_with_prefix(s: &str) -> String {
format!("{}{}", TABLE_PREFIX, s)
}
fn users_table_name() -> String {
table_name_with_prefix("users")
}
fn campaigns_table_name() -> String {
table_name_with_prefix("campaigns")
}
fn messages_table_name() -> String {
table_name_with_prefix("messages")
}
fn unsubscribes_table_name() -> String {
table_name_with_prefix("unsubscribes")
}
#[derive(Debug)]
enum SmsbbInternalError {
CreateTableFailed(String)
}
fn create_table_failed(table_name: &str) -> SmsbbInternalError {
SmsbbInternalError::CreateTableFailed(table_name.to_string())
}
struct DynamoKeySpecification<'a> {
hash_key: &'a str,
sort_key: Option<&'a str>
}
fn build_key_schema_element(attr_name: &str, key_type: KeyType) -> KeySchemaElement {
KeySchemaElement::builder()
.attribute_name(attr_name)
.key_type(key_type)
.build()
.unwrap()
}
fn string_attribute_definition(name: &str) -> AttributeDefinition {
AttributeDefinition::builder().attribute_name(name).attribute_type(ScalarAttributeType::S).build().unwrap()
}
fn atribute_definitions_for_key<'a>(primary_key: &DynamoKeySpecification) -> Option<Vec<AttributeDefinition>> {
let mut results = vec!(string_attribute_definition(primary_key.hash_key));
match primary_key.sort_key {
Some(sort_key) => {
results.push(string_attribute_definition(sort_key))
}
None => {}
}
Some(results)
}
async fn create_table_if_not_exists<'a>(
client: &DynamoClient,
table_name: &str,
primary_key: &DynamoKeySpecification<'a>
) -> Result<(), SmsbbInternalError> {
let primary_key_schema = build_key_schema_element(primary_key.hash_key, KeyType::Hash);
let mut table_builder = client.create_table()
.set_attribute_definitions(atribute_definitions_for_key(primary_key))
.table_name(table_name)
.key_schema(primary_key_schema)
.billing_mode(BillingMode::PayPerRequest);
match primary_key.sort_key {
Some(sort_key) => {
table_builder = table_builder.key_schema(build_key_schema_element(sort_key, KeyType::Range));
}
None => {}
}
let build_result = table_builder.send().await;
match build_result {
Ok(_) => {},
Err(e) => {
match e.as_service_error() {
None => {
dbg!("{}", e);
return Err(create_table_failed(table_name));
},
Some(cte) => {
match cte {
CreateTableError::ResourceInUseException(_) => {
}
problem => {
dbg!("{}", problem);
return Err(create_table_failed(table_name));
}
}
}
}
}
}
// We're not handling secondary indexes yet.
Ok(())
}
async fn ensure_tables(client: &DynamoClient) -> Result<(), SmsbbInternalError> {
let users_table = users_table_name();
let users_key = DynamoKeySpecification { hash_key: "email", sort_key: None };
create_table_if_not_exists(client, &users_table, &users_key).await?;
let campaign_table = campaigns_table_name();
let campaign_key = DynamoKeySpecification { hash_key: "campaign_name", sort_key: Some("created_at") };
create_table_if_not_exists(client, &campaign_table, &campaign_key).await?;
let messages_table = messages_table_name();
let messages_key = DynamoKeySpecification { hash_key: "campaign_name", sort_key: Some("message_id") };
create_table_if_not_exists(client, &messages_table, &messages_key).await?;
let unsubscribes_table = unsubscribes_table_name();
let unsubscribes_key = DynamoKeySpecification { hash_key: "campaign_name", sort_key: Some("phone_number") };
create_table_if_not_exists(client, &unsubscribes_table, &unsubscribes_key).await?;
Ok(())
}
#[tokio::main]
async fn main() -> Result<(), SmsbbInternalError> {
let region_provider = RegionProviderChain::default_provider()
.or_else("us-east-1");
let config = aws_config::defaults(BehaviorVersion::latest())
.region(region_provider)
.load()
.await;
let dynamo_client = DynamoClient::new(&config);
let ensure_resp = ensure_tables(&dynamo_client).await;
match ensure_resp {
Err(sdk_error) => {
dbg!("{}", sdk_error);
return Err(SmsbbInternalError::CreateTableFailed("woof".to_string()));
},
_ => {}
}
Ok(())
}