1pub mod condition;
10pub mod key_condition;
11pub mod projection;
12pub mod reserved;
13pub mod tokenizer;
14pub mod update;
15
16use crate::errors::DynoxideError;
17use crate::types::AttributeValue;
18use std::cell::RefCell;
19use std::collections::{HashMap, HashSet};
20
21pub fn resolve_name(
23 name: &str,
24 attr_names: &Option<HashMap<String, String>>,
25) -> Result<String, String> {
26 if name.starts_with('#') {
27 match attr_names {
28 Some(map) => map.get(name).cloned().ok_or_else(|| {
29 format!(
30 "Value provided in ExpressionAttributeNames unused in expressions: keys: {{{name}}}"
31 )
32 }),
33 None => Err(format!(
34 "An expression attribute name used in the document path is not defined; attribute name: {name}"
35 )),
36 }
37 } else {
38 Ok(name.to_string())
39 }
40}
41
42pub fn resolve_value<'a>(
44 name: &str,
45 attr_values: &'a Option<HashMap<String, AttributeValue>>,
46) -> Result<&'a AttributeValue, String> {
47 match attr_values {
48 Some(map) => map.get(name).ok_or_else(|| {
49 format!(
50 "Value provided in ExpressionAttributeValues unused in expressions: keys: {{{name}}}"
51 )
52 }),
53 None => Err(format!(
54 "An expression attribute value used in expression is not defined; attribute value: {name}"
55 )),
56 }
57}
58
59pub struct TrackedExpressionAttributes<'a> {
64 pub names: &'a Option<HashMap<String, String>>,
65 pub values: &'a Option<HashMap<String, AttributeValue>>,
66 used_names: RefCell<HashSet<String>>,
67 used_values: RefCell<HashSet<String>>,
68 tracking_enabled: bool,
71}
72
73impl<'a> TrackedExpressionAttributes<'a> {
74 pub fn new(
75 names: &'a Option<HashMap<String, String>>,
76 values: &'a Option<HashMap<String, AttributeValue>>,
77 ) -> Self {
78 Self {
79 names,
80 values,
81 used_names: RefCell::new(HashSet::new()),
82 used_values: RefCell::new(HashSet::new()),
83 tracking_enabled: true,
84 }
85 }
86
87 pub fn without_tracking(
91 names: &'a Option<HashMap<String, String>>,
92 values: &'a Option<HashMap<String, AttributeValue>>,
93 ) -> Self {
94 Self {
95 names,
96 values,
97 used_names: RefCell::new(HashSet::new()),
98 used_values: RefCell::new(HashSet::new()),
99 tracking_enabled: false,
100 }
101 }
102
103 pub fn resolve_name(&self, name: &str) -> Result<String, String> {
105 if name.starts_with('#') {
106 if self.tracking_enabled {
107 self.used_names.borrow_mut().insert(name.to_string());
108 }
109 match self.names {
110 Some(map) => map.get(name).cloned().ok_or_else(|| {
111 format!(
112 "An expression attribute name used in the document path is not defined; attribute name: {name}"
113 )
114 }),
115 None => Err(format!(
116 "An expression attribute name used in the document path is not defined; attribute name: {name}"
117 )),
118 }
119 } else {
120 Ok(name.to_string())
121 }
122 }
123
124 pub fn resolve_value<'b>(&'b self, name: &str) -> Result<&'a AttributeValue, String> {
126 if self.tracking_enabled {
127 self.used_values.borrow_mut().insert(name.to_string());
128 }
129 match self.values {
130 Some(map) => map.get(name).ok_or_else(|| {
131 format!(
132 "An expression attribute value used in expression is not defined; attribute value: {name}"
133 )
134 }),
135 None => Err(format!(
136 "An expression attribute value used in expression is not defined; attribute value: {name}"
137 )),
138 }
139 }
140
141 pub fn track_condition_expr(&self, expr: &condition::ConditionExpr) {
144 self.walk_condition(expr);
145 }
146
147 fn walk_condition(&self, expr: &condition::ConditionExpr) {
148 match expr {
149 condition::ConditionExpr::Comparison { left, op: _, right } => {
150 self.walk_operand(left);
151 self.walk_operand(right);
152 }
153 condition::ConditionExpr::Between { operand, lo, hi } => {
154 self.walk_operand(operand);
155 self.walk_operand(lo);
156 self.walk_operand(hi);
157 }
158 condition::ConditionExpr::In { operand, values } => {
159 self.walk_operand(operand);
160 for v in values {
161 self.walk_operand(v);
162 }
163 }
164 condition::ConditionExpr::AttributeExists(path)
165 | condition::ConditionExpr::AttributeNotExists(path) => {
166 self.walk_path_elements(path);
167 }
168 condition::ConditionExpr::AttributeType(path, op) => {
169 self.walk_path_elements(path);
170 self.walk_operand(op);
171 }
172 condition::ConditionExpr::BeginsWith(a, b)
173 | condition::ConditionExpr::Contains(a, b) => {
174 self.walk_operand(a);
175 self.walk_operand(b);
176 }
177 condition::ConditionExpr::And(l, r) | condition::ConditionExpr::Or(l, r) => {
178 self.walk_condition(l);
179 self.walk_condition(r);
180 }
181 condition::ConditionExpr::Not(inner) => {
182 self.walk_condition(inner);
183 }
184 }
185 }
186
187 fn walk_operand(&self, operand: &condition::Operand) {
188 match operand {
189 condition::Operand::Path(path) | condition::Operand::Size(path) => {
190 self.walk_path_elements(path);
191 }
192 condition::Operand::ValueRef(name) => {
193 self.used_values.borrow_mut().insert(name.clone());
194 }
195 }
196 }
197
198 fn walk_path_elements(&self, path: &[PathElement]) {
199 for elem in path {
200 if let PathElement::Attribute(name) = elem {
201 if name.starts_with('#') {
202 self.used_names.borrow_mut().insert(name.clone());
203 }
204 }
205 }
206 }
207
208 pub fn track_projection_expr(&self, proj: &projection::ProjectionExpr) {
210 for path in &proj.paths {
211 self.walk_path_elements(path);
212 }
213 }
214
215 pub fn track_update_expr(&self, expr: &update::UpdateExpr) {
217 for action in &expr.set_actions {
218 self.walk_path_elements(&action.path);
219 self.walk_set_value(&action.value);
220 }
221 for path in &expr.remove_actions {
222 self.walk_path_elements(path);
223 }
224 for action in &expr.add_actions {
225 self.walk_path_elements(&action.path);
226 self.used_values
227 .borrow_mut()
228 .insert(action.value_ref.clone());
229 }
230 for action in &expr.delete_actions {
231 self.walk_path_elements(&action.path);
232 self.used_values
233 .borrow_mut()
234 .insert(action.value_ref.clone());
235 }
236 }
237
238 fn walk_set_value(&self, value: &update::SetValue) {
239 match value {
240 update::SetValue::Operand(op) => self.walk_set_operand(op),
241 update::SetValue::Plus(l, r) | update::SetValue::Minus(l, r) => {
242 self.walk_set_operand(l);
243 self.walk_set_operand(r);
244 }
245 }
246 }
247
248 fn walk_set_operand(&self, operand: &update::SetOperand) {
249 match operand {
250 update::SetOperand::Path(path) => self.walk_path_elements(path),
251 update::SetOperand::ValueRef(name) => {
252 self.used_values.borrow_mut().insert(name.clone());
253 }
254 update::SetOperand::IfNotExists(path, default) => {
255 self.walk_path_elements(path);
256 self.walk_set_operand(default);
257 }
258 update::SetOperand::ListAppend(a, b) => {
259 self.walk_set_operand(a);
260 self.walk_set_operand(b);
261 }
262 update::SetOperand::Group(inner) => self.walk_set_value(inner),
263 }
264 }
265
266 pub fn track_key_condition(&self, cond: &key_condition::KeyCondition) {
270 self.used_values
271 .borrow_mut()
272 .insert(cond.pk_value_ref.clone());
273 if let Some(ref sk) = cond.sk_condition {
274 match sk {
275 key_condition::SortKeyCondition::Eq(_, vr)
276 | key_condition::SortKeyCondition::Lt(_, vr)
277 | key_condition::SortKeyCondition::Le(_, vr)
278 | key_condition::SortKeyCondition::Gt(_, vr)
279 | key_condition::SortKeyCondition::Ge(_, vr)
280 | key_condition::SortKeyCondition::BeginsWith(_, vr) => {
281 self.used_values.borrow_mut().insert(vr.clone());
282 }
283 key_condition::SortKeyCondition::Between(_, lo, hi) => {
284 self.used_values.borrow_mut().insert(lo.clone());
285 self.used_values.borrow_mut().insert(hi.clone());
286 }
287 }
288 }
289 }
290
291 pub fn check_unused(&self) -> Result<(), DynoxideError> {
293 let used_names = self.used_names.borrow();
294 let used_values = self.used_values.borrow();
295
296 if let Some(names_map) = self.names {
297 let unused: Vec<&String> = names_map
298 .keys()
299 .filter(|k| !used_names.contains(*k))
300 .collect();
301 if !unused.is_empty() {
302 let mut keys: Vec<&str> = unused.iter().map(|s| s.as_str()).collect();
303 keys.sort();
304 return Err(DynoxideError::ValidationException(format!(
305 "Value provided in ExpressionAttributeNames unused in expressions: keys: {{{}}}",
306 keys.join(", ")
307 )));
308 }
309 }
310
311 if let Some(values_map) = self.values {
312 let unused: Vec<&String> = values_map
313 .keys()
314 .filter(|k| !used_values.contains(*k))
315 .collect();
316 if !unused.is_empty() {
317 let mut keys: Vec<&str> = unused.iter().map(|s| s.as_str()).collect();
318 keys.sort();
319 return Err(DynoxideError::ValidationException(format!(
320 "Value provided in ExpressionAttributeValues unused in expressions: keys: {{{}}}",
321 keys.join(", ")
322 )));
323 }
324 }
325
326 Ok(())
327 }
328}
329
330pub fn resolve_path_elements(
334 path: &[PathElement],
335 tracker: &TrackedExpressionAttributes,
336) -> Result<Vec<PathElement>, String> {
337 path.iter()
338 .map(|elem| match elem {
339 PathElement::Attribute(name) if name.starts_with('#') => {
340 let resolved = tracker.resolve_name(name)?;
341 Ok(PathElement::Attribute(resolved))
342 }
343 other => Ok(other.clone()),
344 })
345 .collect()
346}
347
348pub fn evaluate_without_tracking(
353 expr: &condition::ConditionExpr,
354 item: &HashMap<String, AttributeValue>,
355 attr_names: &Option<HashMap<String, String>>,
356 attr_values: &Option<HashMap<String, AttributeValue>>,
357) -> Result<bool, String> {
358 let tracker = TrackedExpressionAttributes::without_tracking(attr_names, attr_values);
359 condition::evaluate(expr, item, &tracker)
360}
361
362pub fn resolve_path(
364 item: &HashMap<String, AttributeValue>,
365 path: &[PathElement],
366) -> Option<AttributeValue> {
367 if path.is_empty() {
368 return None;
369 }
370
371 let first = match &path[0] {
372 PathElement::Attribute(name) => item.get(name)?,
373 PathElement::Index(_) => return None,
374 };
375
376 let mut current = first.clone();
377 for element in &path[1..] {
378 match element {
379 PathElement::Attribute(name) => {
380 if let AttributeValue::M(map) = ¤t {
381 current = map.get(name)?.clone();
382 } else {
383 return None;
384 }
385 }
386 PathElement::Index(i) => {
387 if let AttributeValue::L(list) = ¤t {
388 current = list.get(*i)?.clone();
389 } else {
390 return None;
391 }
392 }
393 }
394 }
395
396 Some(current)
397}
398
399pub fn set_path(
402 item: &mut HashMap<String, AttributeValue>,
403 path: &[PathElement],
404 value: AttributeValue,
405) -> Result<(), String> {
406 if path.is_empty() {
407 return Err("Empty path".to_string());
408 }
409
410 if path.len() == 1 {
411 match &path[0] {
412 PathElement::Attribute(name) => {
413 item.insert(name.clone(), value);
414 Ok(())
415 }
416 PathElement::Index(_) => Err("Cannot index into top-level item".to_string()),
417 }
418 } else {
419 let first_name = match &path[0] {
420 PathElement::Attribute(name) => name.clone(),
421 PathElement::Index(_) => return Err("Cannot index into top-level item".to_string()),
422 };
423
424 let entry = match item.get_mut(&first_name) {
428 Some(e) => e,
429 None => {
430 return Err(
431 "The document path provided in the update expression is invalid for update"
432 .to_string(),
433 );
434 }
435 };
436
437 set_nested(entry, &path[1..], value)
438 }
439}
440
441fn pad_list_to(list: &mut Vec<AttributeValue>, target_len: usize) {
443 while list.len() < target_len {
444 list.push(AttributeValue::NULL(true));
445 }
446}
447
448fn set_nested(
449 current: &mut AttributeValue,
450 path: &[PathElement],
451 value: AttributeValue,
452) -> Result<(), String> {
453 if path.is_empty() {
454 return Err("Empty remaining path".to_string());
455 }
456
457 if matches!(current, AttributeValue::NULL(_)) {
461 match &path[0] {
462 PathElement::Attribute(_) => {
463 *current = AttributeValue::M(HashMap::new());
464 }
465 PathElement::Index(_) => {
466 *current = AttributeValue::L(Vec::new());
467 }
468 }
469 }
470
471 if path.len() == 1 {
472 match &path[0] {
473 PathElement::Attribute(name) => {
474 if let AttributeValue::M(map) = current {
475 map.insert(name.clone(), value);
476 Ok(())
477 } else {
478 Err(
479 "The document path provided in the update expression is invalid for update"
480 .to_string(),
481 )
482 }
483 }
484 PathElement::Index(i) => {
485 if let AttributeValue::L(list) = current {
486 pad_list_to(list, *i + 1);
487 list[*i] = value;
488 Ok(())
489 } else {
490 Err(
491 "The document path provided in the update expression is invalid for update"
492 .to_string(),
493 )
494 }
495 }
496 }
497 } else {
498 match &path[0] {
499 PathElement::Attribute(name) => {
500 if let AttributeValue::M(map) = current {
501 match map.get_mut(name) {
504 Some(entry) => set_nested(entry, &path[1..], value),
505 None => Err(
506 "The document path provided in the update expression is invalid for update"
507 .to_string(),
508 ),
509 }
510 } else {
511 Err(
512 "The document path provided in the update expression is invalid for update"
513 .to_string(),
514 )
515 }
516 }
517 PathElement::Index(i) => {
518 if let AttributeValue::L(list) = current {
519 pad_list_to(list, *i + 1);
520 set_nested(&mut list[*i], &path[1..], value)
521 } else {
522 Err(
523 "The document path provided in the update expression is invalid for update"
524 .to_string(),
525 )
526 }
527 }
528 }
529 }
530}
531
532pub fn remove_path(
534 item: &mut HashMap<String, AttributeValue>,
535 path: &[PathElement],
536) -> Result<(), String> {
537 if path.is_empty() {
538 return Err("Empty path".to_string());
539 }
540
541 if path.len() == 1 {
542 match &path[0] {
543 PathElement::Attribute(name) => {
544 item.remove(name);
545 Ok(())
546 }
547 PathElement::Index(_) => Err("Cannot index into top-level item".to_string()),
548 }
549 } else {
550 let first_name = match &path[0] {
551 PathElement::Attribute(name) => name.clone(),
552 PathElement::Index(_) => return Err("Cannot index into top-level item".to_string()),
553 };
554
555 if let Some(entry) = item.get_mut(&first_name) {
556 remove_nested(entry, &path[1..])
557 } else {
558 Ok(()) }
560 }
561}
562
563fn remove_nested(current: &mut AttributeValue, path: &[PathElement]) -> Result<(), String> {
564 if path.is_empty() {
565 return Err("Empty remaining path".to_string());
566 }
567
568 if path.len() == 1 {
569 match &path[0] {
570 PathElement::Attribute(name) => {
571 if let AttributeValue::M(map) = current {
572 map.remove(name);
573 Ok(())
574 } else {
575 Ok(()) }
577 }
578 PathElement::Index(i) => {
579 if let AttributeValue::L(list) = current {
580 if *i < list.len() {
581 list.remove(*i);
582 }
583 Ok(())
584 } else {
585 Ok(()) }
587 }
588 }
589 } else {
590 match &path[0] {
591 PathElement::Attribute(name) => {
592 if let AttributeValue::M(map) = current {
593 if let Some(entry) = map.get_mut(name) {
594 remove_nested(entry, &path[1..])
595 } else {
596 Ok(())
597 }
598 } else {
599 Ok(())
600 }
601 }
602 PathElement::Index(i) => {
603 if let AttributeValue::L(list) = current {
604 if let Some(entry) = list.get_mut(*i) {
605 remove_nested(entry, &path[1..])
606 } else {
607 Ok(())
608 }
609 } else {
610 Ok(())
611 }
612 }
613 }
614 }
615}
616
617#[derive(Debug, Clone, PartialEq)]
619pub enum PathElement {
620 Attribute(String),
621 Index(usize),
622}