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 }
263 }
264
265 pub fn track_key_condition(&self, cond: &key_condition::KeyCondition) {
269 self.used_values
270 .borrow_mut()
271 .insert(cond.pk_value_ref.clone());
272 if let Some(ref sk) = cond.sk_condition {
273 match sk {
274 key_condition::SortKeyCondition::Eq(_, vr)
275 | key_condition::SortKeyCondition::Lt(_, vr)
276 | key_condition::SortKeyCondition::Le(_, vr)
277 | key_condition::SortKeyCondition::Gt(_, vr)
278 | key_condition::SortKeyCondition::Ge(_, vr)
279 | key_condition::SortKeyCondition::BeginsWith(_, vr) => {
280 self.used_values.borrow_mut().insert(vr.clone());
281 }
282 key_condition::SortKeyCondition::Between(_, lo, hi) => {
283 self.used_values.borrow_mut().insert(lo.clone());
284 self.used_values.borrow_mut().insert(hi.clone());
285 }
286 }
287 }
288 }
289
290 pub fn check_unused(&self) -> Result<(), DynoxideError> {
292 let used_names = self.used_names.borrow();
293 let used_values = self.used_values.borrow();
294
295 if let Some(names_map) = self.names {
296 let unused: Vec<&String> = names_map
297 .keys()
298 .filter(|k| !used_names.contains(*k))
299 .collect();
300 if !unused.is_empty() {
301 let mut keys: Vec<&str> = unused.iter().map(|s| s.as_str()).collect();
302 keys.sort();
303 return Err(DynoxideError::ValidationException(format!(
304 "Value provided in ExpressionAttributeNames unused in expressions: keys: {{{}}}",
305 keys.join(", ")
306 )));
307 }
308 }
309
310 if let Some(values_map) = self.values {
311 let unused: Vec<&String> = values_map
312 .keys()
313 .filter(|k| !used_values.contains(*k))
314 .collect();
315 if !unused.is_empty() {
316 let mut keys: Vec<&str> = unused.iter().map(|s| s.as_str()).collect();
317 keys.sort();
318 return Err(DynoxideError::ValidationException(format!(
319 "Value provided in ExpressionAttributeValues unused in expressions: keys: {{{}}}",
320 keys.join(", ")
321 )));
322 }
323 }
324
325 Ok(())
326 }
327}
328
329pub fn resolve_path_elements(
333 path: &[PathElement],
334 tracker: &TrackedExpressionAttributes,
335) -> Result<Vec<PathElement>, String> {
336 path.iter()
337 .map(|elem| match elem {
338 PathElement::Attribute(name) if name.starts_with('#') => {
339 let resolved = tracker.resolve_name(name)?;
340 Ok(PathElement::Attribute(resolved))
341 }
342 other => Ok(other.clone()),
343 })
344 .collect()
345}
346
347pub fn evaluate_without_tracking(
352 expr: &condition::ConditionExpr,
353 item: &HashMap<String, AttributeValue>,
354 attr_names: &Option<HashMap<String, String>>,
355 attr_values: &Option<HashMap<String, AttributeValue>>,
356) -> Result<bool, String> {
357 let tracker = TrackedExpressionAttributes::without_tracking(attr_names, attr_values);
358 condition::evaluate(expr, item, &tracker)
359}
360
361pub fn resolve_path(
363 item: &HashMap<String, AttributeValue>,
364 path: &[PathElement],
365) -> Option<AttributeValue> {
366 if path.is_empty() {
367 return None;
368 }
369
370 let first = match &path[0] {
371 PathElement::Attribute(name) => item.get(name)?,
372 PathElement::Index(_) => return None,
373 };
374
375 let mut current = first.clone();
376 for element in &path[1..] {
377 match element {
378 PathElement::Attribute(name) => {
379 if let AttributeValue::M(map) = ¤t {
380 current = map.get(name)?.clone();
381 } else {
382 return None;
383 }
384 }
385 PathElement::Index(i) => {
386 if let AttributeValue::L(list) = ¤t {
387 current = list.get(*i)?.clone();
388 } else {
389 return None;
390 }
391 }
392 }
393 }
394
395 Some(current)
396}
397
398pub fn set_path(
401 item: &mut HashMap<String, AttributeValue>,
402 path: &[PathElement],
403 value: AttributeValue,
404) -> Result<(), String> {
405 if path.is_empty() {
406 return Err("Empty path".to_string());
407 }
408
409 if path.len() == 1 {
410 match &path[0] {
411 PathElement::Attribute(name) => {
412 item.insert(name.clone(), value);
413 Ok(())
414 }
415 PathElement::Index(_) => Err("Cannot index into top-level item".to_string()),
416 }
417 } else {
418 let first_name = match &path[0] {
419 PathElement::Attribute(name) => name.clone(),
420 PathElement::Index(_) => return Err("Cannot index into top-level item".to_string()),
421 };
422
423 let entry = match item.get_mut(&first_name) {
427 Some(e) => e,
428 None => {
429 return Err(
430 "The document path provided in the update expression is invalid for update"
431 .to_string(),
432 );
433 }
434 };
435
436 set_nested(entry, &path[1..], value)
437 }
438}
439
440fn pad_list_to(list: &mut Vec<AttributeValue>, target_len: usize) {
442 while list.len() < target_len {
443 list.push(AttributeValue::NULL(true));
444 }
445}
446
447fn set_nested(
448 current: &mut AttributeValue,
449 path: &[PathElement],
450 value: AttributeValue,
451) -> Result<(), String> {
452 if path.is_empty() {
453 return Err("Empty remaining path".to_string());
454 }
455
456 if matches!(current, AttributeValue::NULL(_)) {
460 match &path[0] {
461 PathElement::Attribute(_) => {
462 *current = AttributeValue::M(HashMap::new());
463 }
464 PathElement::Index(_) => {
465 *current = AttributeValue::L(Vec::new());
466 }
467 }
468 }
469
470 if path.len() == 1 {
471 match &path[0] {
472 PathElement::Attribute(name) => {
473 if let AttributeValue::M(map) = current {
474 map.insert(name.clone(), value);
475 Ok(())
476 } else {
477 Err(
478 "The document path provided in the update expression is invalid for update"
479 .to_string(),
480 )
481 }
482 }
483 PathElement::Index(i) => {
484 if let AttributeValue::L(list) = current {
485 pad_list_to(list, *i + 1);
486 list[*i] = value;
487 Ok(())
488 } else {
489 Err(
490 "The document path provided in the update expression is invalid for update"
491 .to_string(),
492 )
493 }
494 }
495 }
496 } else {
497 match &path[0] {
498 PathElement::Attribute(name) => {
499 if let AttributeValue::M(map) = current {
500 match map.get_mut(name) {
503 Some(entry) => set_nested(entry, &path[1..], value),
504 None => Err(
505 "The document path provided in the update expression is invalid for update"
506 .to_string(),
507 ),
508 }
509 } else {
510 Err(
511 "The document path provided in the update expression is invalid for update"
512 .to_string(),
513 )
514 }
515 }
516 PathElement::Index(i) => {
517 if let AttributeValue::L(list) = current {
518 pad_list_to(list, *i + 1);
519 set_nested(&mut list[*i], &path[1..], value)
520 } else {
521 Err(
522 "The document path provided in the update expression is invalid for update"
523 .to_string(),
524 )
525 }
526 }
527 }
528 }
529}
530
531pub fn remove_path(
533 item: &mut HashMap<String, AttributeValue>,
534 path: &[PathElement],
535) -> Result<(), String> {
536 if path.is_empty() {
537 return Err("Empty path".to_string());
538 }
539
540 if path.len() == 1 {
541 match &path[0] {
542 PathElement::Attribute(name) => {
543 item.remove(name);
544 Ok(())
545 }
546 PathElement::Index(_) => Err("Cannot index into top-level item".to_string()),
547 }
548 } else {
549 let first_name = match &path[0] {
550 PathElement::Attribute(name) => name.clone(),
551 PathElement::Index(_) => return Err("Cannot index into top-level item".to_string()),
552 };
553
554 if let Some(entry) = item.get_mut(&first_name) {
555 remove_nested(entry, &path[1..])
556 } else {
557 Ok(()) }
559 }
560}
561
562fn remove_nested(current: &mut AttributeValue, path: &[PathElement]) -> Result<(), String> {
563 if path.is_empty() {
564 return Err("Empty remaining path".to_string());
565 }
566
567 if path.len() == 1 {
568 match &path[0] {
569 PathElement::Attribute(name) => {
570 if let AttributeValue::M(map) = current {
571 map.remove(name);
572 Ok(())
573 } else {
574 Ok(()) }
576 }
577 PathElement::Index(i) => {
578 if let AttributeValue::L(list) = current {
579 if *i < list.len() {
580 list.remove(*i);
581 }
582 Ok(())
583 } else {
584 Ok(()) }
586 }
587 }
588 } else {
589 match &path[0] {
590 PathElement::Attribute(name) => {
591 if let AttributeValue::M(map) = current {
592 if let Some(entry) = map.get_mut(name) {
593 remove_nested(entry, &path[1..])
594 } else {
595 Ok(())
596 }
597 } else {
598 Ok(())
599 }
600 }
601 PathElement::Index(i) => {
602 if let AttributeValue::L(list) = current {
603 if let Some(entry) = list.get_mut(*i) {
604 remove_nested(entry, &path[1..])
605 } else {
606 Ok(())
607 }
608 } else {
609 Ok(())
610 }
611 }
612 }
613 }
614}
615
616#[derive(Debug, Clone, PartialEq)]
618pub enum PathElement {
619 Attribute(String),
620 Index(usize),
621}