diff --git a/src/app.rs b/src/app.rs index 03790c8a..370c073a 100644 --- a/src/app.rs +++ b/src/app.rs @@ -24,7 +24,7 @@ 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, @@ -32,6 +32,7 @@ use std::{ }; use super::control; +use super::key; /* ================================================= struct / enum / const @@ -52,8 +53,8 @@ pub enum DyneinFileType { pub struct TableSchema { pub region: String, pub name: String, - pub pk: Key, - pub sk: Option, + pub pk: key::Key, + pub sk: Option, pub indexes: Option>, pub mode: control::Mode, } @@ -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, -} - -#[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 " ()", 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 { - 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, } #[derive(Serialize, Deserialize, Debug, Clone)] @@ -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), }, @@ -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 { - // 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 { - // 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) -> TableSchema { @@ -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), } @@ -612,9 +504,9 @@ pub fn index_schemas(desc: &TableDescription) -> Option> { 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), }); } }; @@ -624,9 +516,9 @@ pub fn index_schemas(desc: &TableDescription) -> Option> { 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), }); } }; diff --git a/src/control.rs b/src/control.rs index b7f2ccd5..9c2f8b8c 100644 --- a/src/control.rs +++ b/src/control.rs @@ -35,6 +35,7 @@ use dialoguer::{theme::ColorfulTheme, Confirm, Select}; use tabwriter::TabWriter; use super::app; +use super::key; /* ================================================= struct / enum / const @@ -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(), @@ -869,10 +870,10 @@ fn extract_secondary_indexes( 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), }; diff --git a/src/data.rs b/src/data.rs index 0f4784d1..b4c6b1c0 100644 --- a/src/data.rs +++ b/src/data.rs @@ -31,6 +31,7 @@ use tabwriter::TabWriter; // use bytes::Bytes; use super::app; +use super::key; /* ================================================= struct / enum / const @@ -895,7 +896,7 @@ fn generate_query_expressions( let expression: String = String::from("#DYNEIN_PKNAME = :DYNEIN_PKVAL"); let mut names = HashMap::::new(); let mut vals = HashMap::::new(); - let mut sort_key_of_target_table_or_index: Option = None; + let mut sort_key_of_target_table_or_index: Option = None; match index { None => @@ -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, + sort_key: Option, partition_key_expression: &str, sort_key_expression: &str, mut names: HashMap, diff --git a/src/key.rs b/src/key.rs new file mode 100644 index 00000000..8ebccaa9 --- /dev/null +++ b/src/key.rs @@ -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 " ()", 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 { + 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 { + // 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 { + // 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(), + }) +} diff --git a/src/main.rs b/src/main.rs index 07867875..fb426b58 100644 --- a/src/main.rs +++ b/src/main.rs @@ -24,6 +24,7 @@ mod bootstrap; mod cmd; mod control; mod data; +mod key; mod shell; mod transfer;