use super::*;
pub(crate) fn resolve_attr_name(name: &str, expr_attr_names: &HashMap<String, String>) -> String {
if name.starts_with('#') {
expr_attr_names
.get(name)
.cloned()
.unwrap_or_else(|| name.to_string())
} else {
name.to_string()
}
}
pub(crate) fn resolve_path(
path: &str,
item: &HashMap<String, AttributeValue>,
expr_attr_names: &HashMap<String, String>,
) -> Option<Value> {
if !path.contains('.') && !path.contains('[') {
return item.get(&resolve_attr_name(path, expr_attr_names)).cloned();
}
let segs = resolve_projection_path_segments(path, expr_attr_names);
resolve_nested_path_segments(item, &segs)
}
pub(crate) fn project_item(
item: &HashMap<String, AttributeValue>,
body: &Value,
) -> HashMap<String, AttributeValue> {
let projection = body["ProjectionExpression"].as_str();
match projection {
Some(proj) if !proj.is_empty() => {
let expr_attr_names = parse_expression_attribute_names(body);
project_with_expression(item, proj, &expr_attr_names)
}
_ => {
match body["AttributesToGet"].as_array() {
Some(attrs) if !attrs.is_empty() => {
let names = HashMap::new();
let mut result = HashMap::new();
for raw in attrs.iter().filter_map(|v| v.as_str()) {
project_single_path_into(&mut result, item, raw, &names);
}
result
}
_ => item.clone(),
}
}
}
}
pub(crate) fn project_with_expression(
item: &HashMap<String, AttributeValue>,
proj: &str,
expr_attr_names: &HashMap<String, String>,
) -> HashMap<String, AttributeValue> {
let mut result = HashMap::new();
for raw in proj.split(',') {
project_single_path_into(&mut result, item, raw.trim(), expr_attr_names);
}
result
}
fn project_single_path_into(
result: &mut HashMap<String, AttributeValue>,
item: &HashMap<String, AttributeValue>,
raw: &str,
expr_attr_names: &HashMap<String, String>,
) {
if !raw.contains('.') && !raw.contains('[') {
let key = resolve_attr_name(raw, expr_attr_names);
if let Some(v) = item.get(&key) {
result.insert(key, v.clone());
}
} else {
let segs = resolve_projection_path_segments(raw, expr_attr_names);
if let Some(v) = resolve_nested_path_segments(item, &segs) {
insert_nested_value_segments(result, &segs, v);
}
}
}
pub(crate) fn resolve_projection_path_segments(
path: &str,
expr_attr_names: &HashMap<String, String>,
) -> Vec<PathSegment> {
let raw = parse_path_segments(path);
raw.into_iter()
.map(|seg| match seg {
PathSegment::Key(k) => PathSegment::Key(resolve_attr_name(&k, expr_attr_names)),
other => other,
})
.collect()
}
pub(crate) fn resolve_nested_path_segments(
item: &HashMap<String, AttributeValue>,
segments: &[PathSegment],
) -> Option<Value> {
if segments.is_empty() {
return None;
}
let top_key = match &segments[0] {
PathSegment::Key(k) => k.as_str(),
_ => return None,
};
let mut current = item.get(top_key)?.clone();
for segment in &segments[1..] {
match segment {
PathSegment::Key(k) => {
current = current.get("M")?.get(k)?.clone();
}
PathSegment::Index(idx) => {
current = current.get("L")?.get(*idx)?.clone();
}
}
}
Some(current)
}
pub(crate) fn insert_nested_value_segments(
result: &mut HashMap<String, AttributeValue>,
segments: &[PathSegment],
value: Value,
) {
if segments.is_empty() {
return;
}
let top_key = match &segments[0] {
PathSegment::Key(k) => k.clone(),
_ => return,
};
if segments.len() == 1 {
result.insert(top_key, value);
return;
}
let wrapped = wrap_value_in_path(&segments[1..], value);
let existing = result.remove(&top_key);
let merged = match existing {
Some(existing) => merge_attribute_values(existing, wrapped),
None => wrapped,
};
result.insert(top_key, merged);
}
#[cfg(test)]
pub(crate) fn resolve_nested_path(
item: &HashMap<String, AttributeValue>,
path: &str,
) -> Option<Value> {
resolve_nested_path_segments(item, &parse_path_segments(path))
}
pub(crate) fn parse_path_segments(path: &str) -> Vec<PathSegment> {
let mut segments = Vec::new();
let mut current = String::new();
let chars: Vec<char> = path.chars().collect();
let mut i = 0;
while i < chars.len() {
match chars[i] {
'.' => {
if !current.is_empty() {
segments.push(PathSegment::Key(current.clone()));
current.clear();
}
}
'[' => {
if !current.is_empty() {
segments.push(PathSegment::Key(current.clone()));
current.clear();
}
i += 1;
let mut num = String::new();
while i < chars.len() && chars[i] != ']' {
num.push(chars[i]);
i += 1;
}
if let Ok(idx) = num.parse::<usize>() {
segments.push(PathSegment::Index(idx));
}
}
c => {
current.push(c);
}
}
i += 1;
}
if !current.is_empty() {
segments.push(PathSegment::Key(current));
}
segments
}
pub(crate) fn wrap_value_in_path(segments: &[PathSegment], value: Value) -> Value {
if segments.is_empty() {
return value;
}
let inner = wrap_value_in_path(&segments[1..], value);
match &segments[0] {
PathSegment::Key(k) => {
json!({"M": {k.clone(): inner}})
}
PathSegment::Index(idx) => {
let mut arr = vec![Value::Null; idx + 1];
arr[*idx] = inner;
json!({"L": arr})
}
}
}
pub(crate) fn merge_attribute_values(a: Value, b: Value) -> Value {
if let (Some(a_map), Some(b_map)) = (
a.get("M").and_then(|v| v.as_object()),
b.get("M").and_then(|v| v.as_object()),
) {
let mut merged = a_map.clone();
for (k, v) in b_map {
if let Some(existing) = merged.get(k) {
merged.insert(
k.clone(),
merge_attribute_values(existing.clone(), v.clone()),
);
} else {
merged.insert(k.clone(), v.clone());
}
}
return json!({"M": merged});
}
if let (Some(a_list), Some(b_list)) = (
a.get("L").and_then(|v| v.as_array()),
b.get("L").and_then(|v| v.as_array()),
) {
let len = a_list.len().max(b_list.len());
let mut out = Vec::with_capacity(len);
for i in 0..len {
let lhs = a_list.get(i).cloned().unwrap_or(Value::Null);
let rhs = b_list.get(i).cloned().unwrap_or(Value::Null);
let picked = if lhs.is_null() {
rhs
} else if rhs.is_null() {
lhs
} else {
merge_attribute_values(lhs, rhs)
};
out.push(picked);
}
return json!({"L": out});
}
b
}