const PARAMETERIZED_TYPE_CAST_ERROR: &str = "class sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl cannot be cast to class java.lang.Class (sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl and java.lang.Class are in module java.base of loader 'bootstrap')";
pub(super) fn pre_check_serialization_types(operation: &str, body: &str) -> crate::Result<()> {
let json: serde_json::Value = serde_json::from_str(body)
.map_err(|e| crate::DynoxideError::SerializationException(e.to_string()))?;
let obj = match json.as_object() {
Some(o) => o,
None => return Ok(()),
};
match operation {
"CreateTable" => {
check_field_is_list(obj, "AttributeDefinitions")?;
check_field_is_list(obj, "KeySchema")?;
check_field_is_list(obj, "LocalSecondaryIndexes")?;
check_field_is_list(obj, "GlobalSecondaryIndexes")?;
check_list_elements_are_structs(obj, "AttributeDefinitions")?;
check_list_elements_are_structs(obj, "KeySchema")?;
check_list_elements_are_structs(obj, "LocalSecondaryIndexes")?;
check_list_elements_are_structs(obj, "GlobalSecondaryIndexes")?;
check_field_is_struct(obj, "ProvisionedThroughput")?;
check_nested_pt_fields(obj)?;
check_nested_list_structs(obj, "KeySchema")?;
check_nested_list_structs(obj, "AttributeDefinitions")?;
if let Some(serde_json::Value::Array(arr)) = obj.get("LocalSecondaryIndexes") {
for item in arr {
if let Some(inner) = item.as_object() {
check_field_is_struct(inner, "Projection")?;
check_field_is_list(inner, "KeySchema")?;
check_list_elements_are_structs(inner, "KeySchema")?;
check_field_is_string(inner, "IndexName")?;
check_nested_list_structs(inner, "KeySchema")?;
check_nested_projection_fields(inner)?;
if let Some(proj) = inner.get("Projection").and_then(|p| p.as_object()) {
check_field_is_list(proj, "NonKeyAttributes")?;
check_nested_list_strings(proj, "NonKeyAttributes")?;
}
}
}
}
if let Some(serde_json::Value::Array(arr)) = obj.get("GlobalSecondaryIndexes") {
for item in arr {
if let Some(inner) = item.as_object() {
check_field_is_struct(inner, "Projection")?;
check_field_is_struct(inner, "ProvisionedThroughput")?;
check_field_is_list(inner, "KeySchema")?;
check_list_elements_are_structs(inner, "KeySchema")?;
check_field_is_string(inner, "IndexName")?;
check_nested_list_structs(inner, "KeySchema")?;
check_nested_projection_fields(inner)?;
check_nested_pt_fields(inner)?;
if let Some(proj) = inner.get("Projection").and_then(|p| p.as_object()) {
check_field_is_list(proj, "NonKeyAttributes")?;
check_nested_list_strings(proj, "NonKeyAttributes")?;
}
}
}
}
}
"UpdateTable" => {
check_field_is_list(obj, "GlobalSecondaryIndexUpdates")?;
check_list_elements_are_structs(obj, "GlobalSecondaryIndexUpdates")?;
check_field_is_struct(obj, "ProvisionedThroughput")?;
check_nested_pt_fields(obj)?;
if let Some(serde_json::Value::Array(arr)) = obj.get("GlobalSecondaryIndexUpdates") {
for item in arr {
if let Some(inner) = item.as_object() {
check_field_is_struct(inner, "Create")?;
check_field_is_struct(inner, "Update")?;
check_field_is_struct(inner, "Delete")?;
if let Some(create) = inner.get("Create").and_then(|v| v.as_object()) {
check_field_is_struct(create, "Projection")?;
check_field_is_struct(create, "ProvisionedThroughput")?;
check_field_is_list(create, "KeySchema")?;
check_list_elements_are_structs(create, "KeySchema")?;
check_nested_list_structs(create, "KeySchema")?;
check_nested_projection_fields(create)?;
check_nested_pt_fields(create)?;
}
if let Some(update) = inner.get("Update").and_then(|v| v.as_object()) {
check_field_is_struct(update, "ProvisionedThroughput")?;
check_nested_pt_fields(update)?;
}
}
}
}
}
"PutItem" | "DeleteItem" | "UpdateItem" => {
check_field_is_map(
obj,
"AttributeUpdates",
"com.amazonaws.dynamodb.v20120810.AttributeValueUpdate",
)?;
check_map_values_are_structs(obj, "AttributeUpdates")?;
}
"Query" => {
check_field_is_map(
obj,
"KeyConditions",
"com.amazonaws.dynamodb.v20120810.Condition",
)?;
check_field_is_map(
obj,
"QueryFilter",
"com.amazonaws.dynamodb.v20120810.Condition",
)?;
check_map_values_are_structs(obj, "QueryFilter")?;
check_map_values_are_structs(obj, "KeyConditions")?;
check_filter_inner_fields(obj, "QueryFilter")?;
check_filter_inner_fields(obj, "KeyConditions")?;
check_filter_attribute_value_lists(obj, "QueryFilter")?;
check_field_is_map(
obj,
"ExclusiveStartKey",
"com.amazonaws.dynamodb.v20120810.AttributeValue",
)?;
}
"Scan" => {
check_field_is_map(
obj,
"ScanFilter",
"com.amazonaws.dynamodb.v20120810.Condition",
)?;
check_map_values_are_structs(obj, "ScanFilter")?;
check_filter_inner_fields(obj, "ScanFilter")?;
check_filter_attribute_value_lists(obj, "ScanFilter")?;
check_field_is_map(
obj,
"ExclusiveStartKey",
"com.amazonaws.dynamodb.v20120810.AttributeValue",
)?;
}
"BatchGetItem" => {
check_field_is_map(
obj,
"RequestItems",
"com.amazonaws.dynamodb.v20120810.KeysAndAttributes",
)?;
check_map_values_are_structs(obj, "RequestItems")?;
if let Some(serde_json::Value::Object(ri)) = obj.get("RequestItems") {
for (_table, val) in ri {
if let Some(inner) = val.as_object() {
check_field_is_map(inner, "ExpressionAttributeNames", "java.lang.String")?;
if let Some(serde_json::Value::Array(keys)) = inner.get("Keys") {
for key in keys {
if !key.is_object() && !key.is_null() {
return Err(crate::DynoxideError::SerializationException(
PARAMETERIZED_TYPE_CAST_ERROR.to_string(),
));
}
if let Some(key_map) = key.as_object() {
for (_k, v) in key_map {
if !v.is_object() && !v.is_null() {
return Err(
crate::DynoxideError::SerializationException(
"Unexpected value type in payload".to_string(),
),
);
}
}
}
}
}
}
}
}
}
"BatchWriteItem" => {
check_field_is_map(
obj,
"RequestItems",
"java.util.List<com.amazonaws.dynamodb.v20120810.WriteRequest>",
)?;
if let Some(serde_json::Value::Object(ri)) = obj.get("RequestItems") {
for (_table, val) in ri {
if !val.is_array() && !val.is_null() {
return Err(crate::DynoxideError::SerializationException(
PARAMETERIZED_TYPE_CAST_ERROR.to_string(),
));
}
if let Some(items) = val.as_array() {
for item in items {
if !item.is_object() && !item.is_null() {
let msg = if item.is_array() {
"Unrecognized collection type class com.amazonaws.dynamodb.v20120810.WriteRequest".to_string()
} else {
"Unexpected value type in payload".to_string()
};
return Err(crate::DynoxideError::SerializationException(msg));
}
}
for item in items {
if let Some(inner) = item.as_object() {
check_field_is_struct(inner, "DeleteRequest")?;
check_field_is_struct(inner, "PutRequest")?;
if let Some(dr) =
inner.get("DeleteRequest").and_then(|v| v.as_object())
{
check_field_is_map(
dr,
"Key",
"com.amazonaws.dynamodb.v20120810.AttributeValue",
)?;
check_map_values_are_structs(dr, "Key")?;
}
if let Some(pr) =
inner.get("PutRequest").and_then(|v| v.as_object())
{
check_field_is_map(
pr,
"Item",
"com.amazonaws.dynamodb.v20120810.AttributeValue",
)?;
check_map_values_are_structs(pr, "Item")?;
}
}
}
}
}
}
}
"TagResource" => {
check_field_is_list(obj, "Tags")?;
check_list_elements_are_structs(obj, "Tags")?;
}
_ => {}
}
check_field_is_map(
obj,
"Key",
"com.amazonaws.dynamodb.v20120810.AttributeValue",
)?;
check_field_is_map(
obj,
"Item",
"com.amazonaws.dynamodb.v20120810.AttributeValue",
)?;
check_field_is_map(obj, "ExpressionAttributeNames", "java.lang.String")?;
check_field_is_map(
obj,
"ExpressionAttributeValues",
"com.amazonaws.dynamodb.v20120810.AttributeValue",
)?;
check_field_is_map(
obj,
"Expected",
"com.amazonaws.dynamodb.v20120810.ExpectedAttributeValue",
)?;
check_map_values_are_structs(obj, "Key")?;
check_map_values_are_structs(obj, "Item")?;
check_map_values_are_structs(obj, "ExpressionAttributeValues")?;
check_map_values_are_structs(obj, "ExclusiveStartKey")?;
check_map_values_are_structs(obj, "Expected")?;
if let Some(serde_json::Value::Object(expected)) = obj.get("Expected") {
for (_attr, cond) in expected {
if let Some(cond_obj) = cond.as_object() {
check_field_is_bool(cond_obj, "Exists")?;
}
}
}
check_field_is_string(obj, "TableName")?;
check_field_is_string(obj, "IndexName")?;
check_field_is_string(obj, "ReturnConsumedCapacity")?;
check_field_is_string(obj, "ReturnValues")?;
check_field_is_string(obj, "ReturnItemCollectionMetrics")?;
check_field_is_string(obj, "ConditionalOperator")?;
check_field_is_string(obj, "Select")?;
check_field_is_string(obj, "ConditionExpression")?;
check_field_is_string(obj, "FilterExpression")?;
check_field_is_string(obj, "KeyConditionExpression")?;
check_field_is_string(obj, "ProjectionExpression")?;
check_field_is_string(obj, "UpdateExpression")?;
check_field_is_int(obj, "Limit")?;
check_field_is_int(obj, "Segment")?;
check_field_is_int(obj, "TotalSegments")?;
check_field_is_bool(obj, "ScanIndexForward")?;
check_field_is_bool(obj, "ConsistentRead")?;
Ok(())
}
fn check_field_is_integer_typed(
obj: &serde_json::Map<String, serde_json::Value>,
field: &str,
java_type: &str,
) -> crate::Result<()> {
let val = match obj.get(field) {
Some(v) if !v.is_null() => v,
_ => return Ok(()),
};
if val.is_number() {
return Ok(());
}
let msg = if val.is_array() {
format!("Unrecognized collection type class java.lang.{java_type}")
} else if val.is_object() {
"Start of structure or map found where not expected".to_string()
} else if val.is_boolean() {
if val.as_bool() == Some(true) {
format!("TRUE_VALUE cannot be converted to {java_type}")
} else {
format!("FALSE_VALUE cannot be converted to {java_type}")
}
} else if val.is_string() {
format!("STRING_VALUE cannot be converted to {java_type}")
} else {
"Unexpected field type".to_string()
};
Err(crate::DynoxideError::SerializationException(msg))
}
fn check_field_is_integer(
obj: &serde_json::Map<String, serde_json::Value>,
field: &str,
) -> crate::Result<()> {
check_field_is_integer_typed(obj, field, "Long")
}
fn check_field_is_int(
obj: &serde_json::Map<String, serde_json::Value>,
field: &str,
) -> crate::Result<()> {
check_field_is_integer_typed(obj, field, "Integer")
}
fn check_field_is_string(
obj: &serde_json::Map<String, serde_json::Value>,
field: &str,
) -> crate::Result<()> {
let val = match obj.get(field) {
Some(v) if !v.is_null() => v,
_ => return Ok(()),
};
if val.is_string() {
return Ok(());
}
let msg = if val.is_array() {
"Unrecognized collection type class java.lang.String".to_string()
} else if val.is_object() {
"Start of structure or map found where not expected".to_string()
} else if val.as_bool() == Some(true) {
"TRUE_VALUE cannot be converted to String".to_string()
} else if val.as_bool() == Some(false) {
"FALSE_VALUE cannot be converted to String".to_string()
} else if val.is_number() {
if val.is_f64() && !val.is_i64() && !val.is_u64() {
"DECIMAL_VALUE cannot be converted to String".to_string()
} else {
"NUMBER_VALUE cannot be converted to String".to_string()
}
} else {
"Unexpected field type".to_string()
};
Err(crate::DynoxideError::SerializationException(msg))
}
fn check_field_is_bool(
obj: &serde_json::Map<String, serde_json::Value>,
field: &str,
) -> crate::Result<()> {
let val = match obj.get(field) {
Some(v) if !v.is_null() => v,
_ => return Ok(()),
};
if val.is_boolean() {
return Ok(());
}
let msg = if val.is_array() {
"Unrecognized collection type class java.lang.Boolean".to_string()
} else if val.is_object() {
"Start of structure or map found where not expected".to_string()
} else if val.is_string() {
"Unexpected token received from parser".to_string()
} else if val.is_number() {
if val.is_f64() && !val.is_i64() && !val.is_u64() {
"DECIMAL_VALUE cannot be converted to Boolean".to_string()
} else {
"NUMBER_VALUE cannot be converted to Boolean".to_string()
}
} else {
"Unexpected field type".to_string()
};
Err(crate::DynoxideError::SerializationException(msg))
}
fn check_list_elements_are_structs(
obj: &serde_json::Map<String, serde_json::Value>,
field: &str,
) -> crate::Result<()> {
let java_class = match field {
"KeySchema" => "com.amazonaws.dynamodb.v20120810.KeySchemaElement",
"AttributeDefinitions" => "com.amazonaws.dynamodb.v20120810.AttributeDefinition",
"LocalSecondaryIndexes" => "com.amazonaws.dynamodb.v20120810.LocalSecondaryIndex",
"GlobalSecondaryIndexes" => "com.amazonaws.dynamodb.v20120810.GlobalSecondaryIndex",
"GlobalSecondaryIndexUpdates" => {
"com.amazonaws.dynamodb.v20120810.GlobalSecondaryIndexUpdate"
}
"Tags" => "com.amazonaws.dynamodb.v20120810.Tag",
_ => "Unknown",
};
if let Some(serde_json::Value::Array(arr)) = obj.get(field) {
for item in arr {
if !item.is_object() && !item.is_null() {
let msg = if item.is_array() {
format!("Unrecognized collection type class {java_class}")
} else {
"Unexpected value type in payload".to_string()
};
return Err(crate::DynoxideError::SerializationException(msg));
}
}
}
Ok(())
}
fn check_nested_pt_fields(obj: &serde_json::Map<String, serde_json::Value>) -> crate::Result<()> {
if let Some(pt) = obj.get("ProvisionedThroughput").and_then(|v| v.as_object()) {
check_field_is_integer(pt, "WriteCapacityUnits")?;
check_field_is_integer(pt, "ReadCapacityUnits")?;
}
Ok(())
}
fn check_nested_projection_fields(
obj: &serde_json::Map<String, serde_json::Value>,
) -> crate::Result<()> {
if let Some(proj) = obj.get("Projection").and_then(|v| v.as_object()) {
check_field_is_string(proj, "ProjectionType")?;
}
Ok(())
}
fn check_nested_list_structs(
obj: &serde_json::Map<String, serde_json::Value>,
field: &str,
) -> crate::Result<()> {
if let Some(serde_json::Value::Array(arr)) = obj.get(field) {
for item in arr {
if let Some(inner) = item.as_object() {
check_field_is_string(inner, "KeyType")?;
check_field_is_string(inner, "AttributeName")?;
check_field_is_string(inner, "AttributeType")?;
check_field_is_string(inner, "IndexName")?;
}
}
}
Ok(())
}
fn check_nested_list_strings(
obj: &serde_json::Map<String, serde_json::Value>,
field: &str,
) -> crate::Result<()> {
if let Some(serde_json::Value::Array(arr)) = obj.get(field) {
for item in arr {
if !item.is_string() && !item.is_null() {
if item.is_boolean() {
let val = if item.as_bool() == Some(true) {
"TRUE_VALUE"
} else {
"FALSE_VALUE"
};
return Err(crate::DynoxideError::SerializationException(format!(
"{val} cannot be converted to String"
)));
} else if item.is_number() {
return Err(crate::DynoxideError::SerializationException(
"NUMBER_VALUE cannot be converted to String".to_string(),
));
}
}
}
}
Ok(())
}
fn check_map_values_are_structs(
obj: &serde_json::Map<String, serde_json::Value>,
field: &str,
) -> crate::Result<()> {
let java_class = match field {
"Key" | "Item" | "ExpressionAttributeValues" | "ExclusiveStartKey" => {
"com.amazonaws.dynamodb.v20120810.AttributeValue"
}
"Expected" => "com.amazonaws.dynamodb.v20120810.ExpectedAttributeValue",
"AttributeUpdates" => "com.amazonaws.dynamodb.v20120810.AttributeValueUpdate",
"RequestItems" => "com.amazonaws.dynamodb.v20120810.KeysAndAttributes",
"KeyConditions" | "QueryFilter" | "ScanFilter" => {
"com.amazonaws.dynamodb.v20120810.Condition"
}
_ => "Unknown",
};
if let Some(serde_json::Value::Object(map)) = obj.get(field) {
for (_key, val) in map {
if !val.is_object() && !val.is_null() {
let msg = if val.is_array() {
format!("Unrecognized collection type class {java_class}")
} else {
"Unexpected value type in payload".to_string()
};
return Err(crate::DynoxideError::SerializationException(msg));
}
}
}
Ok(())
}
fn check_field_is_map(
obj: &serde_json::Map<String, serde_json::Value>,
field: &str,
java_value_type: &str,
) -> crate::Result<()> {
let val = match obj.get(field) {
Some(v) if !v.is_null() => v,
_ => return Ok(()),
};
if val.is_object() {
return Ok(());
}
let msg = if val.is_array() {
format!("Unrecognized collection type java.util.Map<java.lang.String, {java_value_type}>")
} else {
"Unexpected field type".to_string()
};
Err(crate::DynoxideError::SerializationException(msg))
}
fn check_field_is_struct(
obj: &serde_json::Map<String, serde_json::Value>,
field: &str,
) -> crate::Result<()> {
let val = match obj.get(field) {
Some(v) if !v.is_null() => v,
_ => return Ok(()),
};
if val.is_object() {
return Ok(());
}
let msg = if val.is_array() {
let dynamo_class = match field {
"ProvisionedThroughput" => {
Some("com.amazonaws.dynamodb.v20120810.ProvisionedThroughput")
}
"Projection" => Some("com.amazonaws.dynamodb.v20120810.Projection"),
"DeleteRequest" => Some("com.amazonaws.dynamodb.v20120810.DeleteRequest"),
"PutRequest" => Some("com.amazonaws.dynamodb.v20120810.PutRequest"),
"Create" => Some("com.amazonaws.dynamodb.v20120810.CreateGlobalSecondaryIndexAction"),
"Update" => Some("com.amazonaws.dynamodb.v20120810.UpdateGlobalSecondaryIndexAction"),
"Delete" => Some("com.amazonaws.dynamodb.v20120810.DeleteGlobalSecondaryIndexAction"),
_ => None,
};
if let Some(cls) = dynamo_class {
format!("Unrecognized collection type class {cls}")
} else {
"Start of structure or map found where not expected".to_string()
}
} else {
"Unexpected field type".to_string()
};
Err(crate::DynoxideError::SerializationException(msg))
}
fn check_field_is_list(
obj: &serde_json::Map<String, serde_json::Value>,
field: &str,
) -> crate::Result<()> {
let val = match obj.get(field) {
Some(v) if !v.is_null() => v,
_ => return Ok(()),
};
if val.is_array() {
return Ok(());
}
let msg = if val.is_object() {
"Start of structure or map found where not expected".to_string()
} else {
"Unexpected field type".to_string()
};
Err(crate::DynoxideError::SerializationException(msg))
}
fn check_filter_inner_fields(
obj: &serde_json::Map<String, serde_json::Value>,
filter_field: &str,
) -> crate::Result<()> {
let filter = match obj.get(filter_field) {
Some(v) if v.is_object() => v.as_object().unwrap(),
_ => return Ok(()),
};
for (_attr_name, condition) in filter {
if let Some(cond_obj) = condition.as_object() {
check_field_is_string(cond_obj, "ComparisonOperator")?;
check_field_is_list(cond_obj, "AttributeValueList")?;
if let Some(serde_json::Value::Array(avl)) = cond_obj.get("AttributeValueList") {
for item in avl {
if !item.is_object() && !item.is_null() {
let msg = if item.is_array() {
"Unrecognized collection type class com.amazonaws.dynamodb.v20120810.AttributeValue"
.to_string()
} else {
"Unexpected value type in payload".to_string()
};
return Err(crate::DynoxideError::SerializationException(msg));
}
}
}
}
}
Ok(())
}
fn check_filter_attribute_value_lists(
obj: &serde_json::Map<String, serde_json::Value>,
filter_field: &str,
) -> crate::Result<()> {
let filter = match obj.get(filter_field) {
Some(v) if v.is_object() => v.as_object().unwrap(),
_ => return Ok(()),
};
for (_attr_name, condition) in filter {
if let Some(cond_obj) = condition.as_object() {
check_field_is_list(cond_obj, "AttributeValueList")?;
}
}
Ok(())
}