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