use crate::actions::create_table::CreateTableRequest;
use std::path::Path;
#[derive(Debug)]
pub struct TableSchema {
pub table_name: String,
pub create_request: CreateTableRequest,
}
pub fn load_schemas(path: &Path) -> Result<(Vec<TableSchema>, serde_json::Value), String> {
let content = std::fs::read_to_string(path)
.map_err(|e| format!("Failed to read schema file {}: {e}", path.display()))?;
let value: serde_json::Value =
serde_json::from_str(&content).map_err(|e| format!("Failed to parse schema JSON: {e}"))?;
let schemas = match &value {
serde_json::Value::Array(arr) => {
let mut schemas = Vec::with_capacity(arr.len());
for (i, item) in arr.iter().enumerate() {
schemas.push(
parse_describe_table_response(item)
.map_err(|e| format!("Schema {}: {e}", i + 1))?,
);
}
schemas
}
serde_json::Value::Object(_) => vec![parse_describe_table_response(&value)?],
_ => return Err("Schema file must contain a JSON object or array".to_string()),
};
Ok((schemas, value))
}
fn parse_describe_table_response(value: &serde_json::Value) -> Result<TableSchema, String> {
let table = value.get("Table").unwrap_or(value);
let table_name = table
.get("TableName")
.and_then(|v| v.as_str())
.ok_or("missing TableName")?
.to_string();
let mut table = table.clone();
super::unwrap_describe_table_shapes(&mut table);
let create_request: CreateTableRequest =
serde_json::from_value(table).map_err(|e| format!("table '{table_name}': {e}"))?;
Ok(TableSchema {
table_name,
create_request,
})
}
#[cfg(test)]
mod tests {
use super::*;
fn sample_describe_table_json() -> serde_json::Value {
serde_json::json!({
"Table": {
"TableName": "Users",
"KeySchema": [
{"AttributeName": "pk", "KeyType": "HASH"},
{"AttributeName": "sk", "KeyType": "RANGE"}
],
"AttributeDefinitions": [
{"AttributeName": "pk", "AttributeType": "S"},
{"AttributeName": "sk", "AttributeType": "S"},
{"AttributeName": "gsi1pk", "AttributeType": "S"}
],
"GlobalSecondaryIndexes": [{
"IndexName": "gsi1",
"KeySchema": [
{"AttributeName": "gsi1pk", "KeyType": "HASH"}
],
"Projection": {
"ProjectionType": "ALL"
}
}]
}
})
}
#[test]
fn test_parse_describe_table_response() {
let schema = parse_describe_table_response(&sample_describe_table_json()).unwrap();
assert_eq!(schema.table_name, "Users");
assert_eq!(schema.create_request.key_schema.len(), 2);
assert_eq!(schema.create_request.attribute_definitions.len(), 3);
assert!(schema.create_request.global_secondary_indexes.is_some());
assert_eq!(
schema
.create_request
.global_secondary_indexes
.as_ref()
.unwrap()
.len(),
1
);
}
#[test]
fn test_parse_without_table_wrapper() {
let json = serde_json::json!({
"TableName": "Simple",
"KeySchema": [
{"AttributeName": "id", "KeyType": "HASH"}
],
"AttributeDefinitions": [
{"AttributeName": "id", "AttributeType": "S"}
]
});
let schema = parse_describe_table_response(&json).unwrap();
assert_eq!(schema.table_name, "Simple");
}
#[test]
fn a_local_secondary_index_survives_the_parse() {
let json = serde_json::json!({"Table": {
"TableName": "Orders",
"KeySchema": [
{"AttributeName": "pk", "KeyType": "HASH"},
{"AttributeName": "sk", "KeyType": "RANGE"}
],
"AttributeDefinitions": [
{"AttributeName": "pk", "AttributeType": "S"},
{"AttributeName": "sk", "AttributeType": "S"},
{"AttributeName": "lsisk", "AttributeType": "S"}
],
"LocalSecondaryIndexes": [{
"IndexName": "LSI1",
"KeySchema": [
{"AttributeName": "pk", "KeyType": "HASH"},
{"AttributeName": "lsisk", "KeyType": "RANGE"}
],
"Projection": {"ProjectionType": "ALL"}
}]
}});
let schema = parse_describe_table_response(&json).unwrap();
let lsis = schema
.create_request
.local_secondary_indexes
.as_deref()
.unwrap_or(&[]);
assert_eq!(
lsis.len(),
1,
"the LSI has to reach the schema the deriver reads"
);
assert_eq!(lsis[0].index_name, "LSI1");
}
#[test]
fn test_parse_missing_table_name() {
let json = serde_json::json!({
"KeySchema": [{"AttributeName": "id", "KeyType": "HASH"}],
"AttributeDefinitions": [{"AttributeName": "id", "AttributeType": "S"}]
});
assert!(parse_describe_table_response(&json).is_err());
}
}