Skip to content

Commit

Permalink
refactor: move key from app.rs to key.rs
Browse files Browse the repository at this point in the history
Key is used by some functions so it should be divided from app.rs
  • Loading branch information
ryota-sakamoto committed May 15, 2023
1 parent 65f7ec9 commit ebe17c2
Show file tree
Hide file tree
Showing 5 changed files with 151 additions and 128 deletions.
136 changes: 14 additions & 122 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,15 @@ use serde_yaml::Error as SerdeYAMLError;
use std::{
collections::HashMap,
env, error,
fmt::{self, Display, Error as FmtError, Formatter},
fmt::{self, Formatter},
fs,
io::Error as IOError,
path,
str::FromStr,
};

use super::control;
use super::key;

/* =================================================
struct / enum / const
Expand All @@ -52,8 +53,8 @@ pub enum DyneinFileType {
pub struct TableSchema {
pub region: String,
pub name: String,
pub pk: Key,
pub sk: Option<Key>,
pub pk: key::Key,
pub sk: Option<key::Key>,
pub indexes: Option<Vec<IndexSchema>>,
pub mode: control::Mode,
}
Expand All @@ -64,86 +65,8 @@ pub struct IndexSchema {
/// Type of index. i.e. GSI (Global Secondary Index) or LSI (Local Secondary Index).
/// Use 'kind' as 'type' is a keyword in Rust.
pub kind: IndexType,
pub pk: Key,
pub sk: Option<Key>,
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Key {
pub name: String,
/// Data type of the primary key. i.e. "S" (String), "N" (Number), or "B" (Binary).
/// Use 'kind' as 'type' is a keyword in Rust.
pub kind: KeyType,
}

impl Key {
/// return String with "<pk name> (<pk type>)", e.g. "myPk (S)". Used in desc command outputs.
pub fn display(&self) -> String {
format!("{} ({})", self.name, self.kind)
}
}

/// Restrict acceptable DynamoDB data types for primary keys.
/// enum witn methods/FromStr ref: https://docs.rs/rusoto_signature/0.42.0/src/rusoto_signature/region.rs.html#226-258
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
pub enum KeyType {
S,
N,
B,
}

/// implement Display for KeyType to simply print a single letter "S", "N", or "B".
impl Display for KeyType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}",
match self {
KeyType::S => "S",
KeyType::N => "N",
KeyType::B => "B",
}
)
}
}

#[derive(Debug, PartialEq)]
pub struct ParseKeyTypeError {
message: String,
}

impl std::error::Error for ParseKeyTypeError {
fn description(&self) -> &str {
&self.message
}
}

impl Display for ParseKeyTypeError {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> {
write!(f, "{}", self.message)
}
}

impl ParseKeyTypeError {
/// Parses a region given as a string literal into a type `KeyType'
pub fn new(input: &str) -> Self {
Self {
message: format!("Not a valid DynamoDB primary key type: {}", input),
}
}
}

impl FromStr for KeyType {
type Err = ParseKeyTypeError;

fn from_str(s: &str) -> Result<Self, ParseKeyTypeError> {
match s {
"S" => Ok(Self::S),
"N" => Ok(Self::N),
"B" => Ok(Self::B),
x => Err(ParseKeyTypeError::new(x)),
}
}
pub pk: key::Key,
pub sk: Option<key::Key>,
}

#[derive(Serialize, Deserialize, Debug, Clone)]
Expand Down Expand Up @@ -500,8 +423,8 @@ pub fn insert_to_table_cache(
TableSchema {
region: String::from(region.name()),
name: table_name,
pk: typed_key("HASH", &desc).expect("pk should exist"),
sk: typed_key("RANGE", &desc),
pk: key::typed_key("HASH", &desc).expect("pk should exist"),
sk: key::typed_key("RANGE", &desc),
indexes: index_schemas(&desc),
mode: control::extract_mode(&desc.billing_mode_summary),
},
Expand Down Expand Up @@ -529,37 +452,6 @@ pub fn remove_dynein_files() -> Result<(), DyneinConfigError> {
Ok(())
}

/// returns Option of a tuple (attribute_name, attribute_type (S/N/B)).
/// Used when you want to know "what is the Partition Key name and its data type of this table".
pub fn typed_key(pk_or_sk: &str, desc: &TableDescription) -> Option<Key> {
// extracting key schema of "base table" here
let ks = desc.clone().key_schema.unwrap();
typed_key_for_schema(pk_or_sk, &ks, &desc.clone().attribute_definitions.unwrap())
}

/// Receives key data type (HASH or RANGE), KeySchemaElement(s), and AttributeDefinition(s),
/// In many cases it's called by typed_key, but when retrieving index schema, this method can be used directly so put it as public.
pub fn typed_key_for_schema(
pk_or_sk: &str,
ks: &[KeySchemaElement],
attrs: &[AttributeDefinition],
) -> Option<Key> {
// Fetch Partition Key ("HASH") or Sort Key ("RANGE") from given Key Schema. pk should always exists, but sk may not.
let target_key = ks.iter().find(|x| x.key_type == pk_or_sk);
target_key.map(|key| Key {
name: key.clone().attribute_name,
// kind should be one of S/N/B, Which can be retrieved from AttributeDefinition's attribute_type.
kind: KeyType::from_str(
&attrs
.iter()
.find(|at| at.attribute_name == key.attribute_name)
.expect("primary key should be in AttributeDefinition.")
.attribute_type,
)
.unwrap(),
})
}

// If you explicitly specify target table by `--table/-t` option, this function executes DescribeTable API to gather table schema info.
// Otherwise, load table schema info from config file.
// fn table_schema(region: &Region, config: &config::Config, table_overwritten: Option<String>) -> TableSchema {
Expand All @@ -577,8 +469,8 @@ pub async fn table_schema(cx: &Context) -> TableSchema {
TableSchema {
region: String::from(cx.effective_region().name()),
name: desc.clone().table_name.unwrap(),
pk: typed_key("HASH", &desc).expect("pk should exist"),
sk: typed_key("RANGE", &desc),
pk: key::typed_key("HASH", &desc).expect("pk should exist"),
sk: key::typed_key("RANGE", &desc),
indexes: index_schemas(&desc),
mode: control::extract_mode(&desc.billing_mode_summary),
}
Expand Down Expand Up @@ -612,9 +504,9 @@ pub fn index_schemas(desc: &TableDescription) -> Option<Vec<IndexSchema>> {
indexes.push(IndexSchema {
name: gsi.index_name.unwrap(),
kind: IndexType::Gsi,
pk: typed_key_for_schema("HASH", &gsi.key_schema.clone().unwrap(), attr_defs)
pk: key::typed_key_for_schema("HASH", &gsi.key_schema.clone().unwrap(), attr_defs)
.expect("pk should exist"),
sk: typed_key_for_schema("RANGE", &gsi.key_schema.unwrap(), attr_defs),
sk: key::typed_key_for_schema("RANGE", &gsi.key_schema.unwrap(), attr_defs),
});
}
};
Expand All @@ -624,9 +516,9 @@ pub fn index_schemas(desc: &TableDescription) -> Option<Vec<IndexSchema>> {
indexes.push(IndexSchema {
name: lsi.index_name.unwrap(),
kind: IndexType::Lsi,
pk: typed_key_for_schema("HASH", &lsi.key_schema.clone().unwrap(), attr_defs)
pk: key::typed_key_for_schema("HASH", &lsi.key_schema.clone().unwrap(), attr_defs)
.expect("pk should exist"),
sk: typed_key_for_schema("RANGE", &lsi.key_schema.unwrap(), attr_defs),
sk: key::typed_key_for_schema("RANGE", &lsi.key_schema.unwrap(), attr_defs),
});
}
};
Expand Down
9 changes: 5 additions & 4 deletions src/control.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ use dialoguer::{theme::ColorfulTheme, Confirm, Select};
use tabwriter::TabWriter;

use super::app;
use super::key;

/* =================================================
struct / enum / const
Expand Down Expand Up @@ -238,10 +239,10 @@ pub fn print_table_description(region: Region, desc: TableDescription) {
region: String::from(region.name()),
status: String::from(&desc.clone().table_status.unwrap()),
schema: PrintPrimaryKeys {
pk: app::typed_key("HASH", &desc)
pk: key::typed_key("HASH", &desc)
.expect("pk should exist")
.display(),
sk: app::typed_key("RANGE", &desc).map(|k| k.display()),
sk: key::typed_key("RANGE", &desc).map(|k| k.display()),
},

mode: mode.clone(),
Expand Down Expand Up @@ -869,10 +870,10 @@ fn extract_secondary_indexes<T: IndexDesc>(
let idx = PrintSecondaryIndex {
name: String::from(idx.retrieve_index_name().as_ref().unwrap()),
schema: PrintPrimaryKeys {
pk: app::typed_key_for_schema("HASH", ks, attr_defs)
pk: key::typed_key_for_schema("HASH", ks, attr_defs)
.expect("pk should exist")
.display(),
sk: app::typed_key_for_schema("RANGE", ks, attr_defs).map(|k| k.display()),
sk: key::typed_key_for_schema("RANGE", ks, attr_defs).map(|k| k.display()),
},
capacity: idx.extract_index_capacity(mode),
};
Expand Down
5 changes: 3 additions & 2 deletions src/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ use tabwriter::TabWriter;
// use bytes::Bytes;

use super::app;
use super::key;

/* =================================================
struct / enum / const
Expand Down Expand Up @@ -895,7 +896,7 @@ fn generate_query_expressions(
let expression: String = String::from("#DYNEIN_PKNAME = :DYNEIN_PKVAL");
let mut names = HashMap::<String, String>::new();
let mut vals = HashMap::<String, AttributeValue>::new();
let mut sort_key_of_target_table_or_index: Option<app::Key> = None;
let mut sort_key_of_target_table_or_index: Option<key::Key> = None;

match index {
None =>
Expand Down Expand Up @@ -978,7 +979,7 @@ fn generate_query_expressions(
/// Using existing key condition expr (e.g. "myId <= :idVal") and supplementary mappings (expression_attribute_names, expression_attribute_values),
/// this method returns GeneratedQueryParams struct. Note that it's called only when sort key expression (ske) exists.
fn append_sort_key_expression(
sort_key: Option<app::Key>,
sort_key: Option<key::Key>,
partition_key_expression: &str,
sort_key_expression: &str,
mut names: HashMap<String, String>,
Expand Down
128 changes: 128 additions & 0 deletions src/key.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

use ::serde::{Deserialize, Serialize};
use rusoto_dynamodb::{AttributeDefinition, KeySchemaElement, TableDescription};
use std::str::FromStr;

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Key {
pub name: String,
/// Data type of the primary key. i.e. "S" (String), "N" (Number), or "B" (Binary).
/// Use 'kind' as 'type' is a keyword in Rust.
pub kind: KeyType,
}

impl Key {
/// return String with "<pk name> (<pk type>)", e.g. "myPk (S)". Used in desc command outputs.
pub fn display(&self) -> String {
format!("{} ({})", self.name, self.kind)
}
}

/// Restrict acceptable DynamoDB data types for primary keys.
/// enum witn methods/FromStr ref: https://docs.rs/rusoto_signature/0.42.0/src/rusoto_signature/region.rs.html#226-258
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
pub enum KeyType {
S,
N,
B,
}

/// implement Display for KeyType to simply print a single letter "S", "N", or "B".
impl std::fmt::Display for KeyType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
KeyType::S => "S",
KeyType::N => "N",
KeyType::B => "B",
}
)
}
}

#[derive(Debug, PartialEq)]
pub struct ParseKeyTypeError {
message: String,
}

impl std::error::Error for ParseKeyTypeError {
fn description(&self) -> &str {
&self.message
}
}

impl std::fmt::Display for ParseKeyTypeError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
write!(f, "{}", self.message)
}
}

impl ParseKeyTypeError {
/// Parses a region given as a string literal into a type `KeyType'
pub fn new(input: &str) -> Self {
Self {
message: format!("Not a valid DynamoDB primary key type: {}", input),
}
}
}

impl FromStr for KeyType {
type Err = ParseKeyTypeError;

fn from_str(s: &str) -> Result<Self, ParseKeyTypeError> {
match s {
"S" => Ok(Self::S),
"N" => Ok(Self::N),
"B" => Ok(Self::B),
x => Err(ParseKeyTypeError::new(x)),
}
}
}

/// returns Option of a tuple (attribute_name, attribute_type (S/N/B)).
/// Used when you want to know "what is the Partition Key name and its data type of this table".
pub fn typed_key(pk_or_sk: &str, desc: &TableDescription) -> Option<Key> {
// extracting key schema of "base table" here
let ks = desc.clone().key_schema.unwrap();
typed_key_for_schema(pk_or_sk, &ks, &desc.clone().attribute_definitions.unwrap())
}

/// Receives key data type (HASH or RANGE), KeySchemaElement(s), and AttributeDefinition(s),
/// In many cases it's called by typed_key, but when retrieving index schema, this method can be used directly so put it as public.
pub fn typed_key_for_schema(
pk_or_sk: &str,
ks: &[KeySchemaElement],
attrs: &[AttributeDefinition],
) -> Option<Key> {
// Fetch Partition Key ("HASH") or Sort Key ("RANGE") from given Key Schema. pk should always exists, but sk may not.
let target_key = ks.iter().find(|x| x.key_type == pk_or_sk);
target_key.map(|key| Key {
name: key.clone().attribute_name,
// kind should be one of S/N/B, Which can be retrieved from AttributeDefinition's attribute_type.
kind: KeyType::from_str(
&attrs
.iter()
.find(|at| at.attribute_name == key.attribute_name)
.expect("primary key should be in AttributeDefinition.")
.attribute_type,
)
.unwrap(),
})
}
1 change: 1 addition & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ mod bootstrap;
mod cmd;
mod control;
mod data;
mod key;
mod shell;
mod transfer;

Expand Down

0 comments on commit ebe17c2

Please sign in to comment.