1use crate::computation::{OperationResult, VetoType};
2use crate::parsing::ast::{ascii_lowercase_logical_name, canonical_logical_name, Value};
3use crate::planning::execution_plan::{validate_value_against_type, ExecutionPlan};
4use crate::planning::instance_list::{
5 default_of, row_end_type, typed_from_bound, InstanceListSource, KeyIndex,
6};
7use crate::planning::semantics::{
8 parse_value_from_string, parser_value_to_value_kind, row_literal, BoundValueKind,
9 DataDefinition, DataPath, InstanceKey, KeyField, LemmaType, ListKey, ListValue, LiteralValue,
10 RowBinding, RowEnd, RowOrigin, Source, SpecIdentity, SpecValue, TypeSpecification,
11 TypedLiteral, UnitTokenResolver, ValueKind,
12};
13use crate::planning::unit_index::UnitIndex;
14use crate::Error;
15use crate::ResourceLimits;
16use indexmap::IndexMap;
17use std::collections::HashSet;
18use std::sync::Arc;
19
20pub enum EvaluateDataValue {
25 Literal(String),
27 Boolean(bool),
28 Map(IndexMap<String, EvaluateDataValue>),
29 List(Vec<EvaluateDataValue>),
30}
31
32impl EvaluateDataValue {
33 pub fn string(value: impl Into<String>) -> Self {
34 Self::Literal(value.into())
35 }
36
37 pub(crate) fn is_empty(&self) -> bool {
38 match self {
39 Self::Literal(s) => s.trim().is_empty(),
40 Self::Boolean(_) | Self::Map(_) | Self::List(_) => false,
41 }
42 }
43
44 pub(crate) fn into_list(mut self) -> Option<Vec<EvaluateDataValue>> {
45 match &mut self {
46 Self::List(items) => Some(std::mem::take(items)),
47 Self::Literal(_) | Self::Boolean(_) | Self::Map(_) => None,
48 }
49 }
50
51 pub(crate) fn into_map(mut self) -> Option<IndexMap<String, EvaluateDataValue>> {
52 match &mut self {
53 Self::Map(map) => Some(std::mem::take(map)),
54 Self::Literal(_) | Self::Boolean(_) | Self::List(_) => None,
55 }
56 }
57}
58
59impl Drop for EvaluateDataValue {
60 fn drop(&mut self) {
61 let mut pending = Vec::new();
62 take_children(self, &mut pending);
63 while let Some(mut node) = pending.pop() {
64 take_children(&mut node, &mut pending);
65 }
66 }
67}
68
69fn take_children(node: &mut EvaluateDataValue, pending: &mut Vec<EvaluateDataValue>) {
70 match node {
71 EvaluateDataValue::Literal(_) | EvaluateDataValue::Boolean(_) => {}
72 EvaluateDataValue::Map(map) => pending.extend(std::mem::take(map).into_values()),
73 EvaluateDataValue::List(items) => pending.append(items),
74 }
75}
76
77impl Clone for EvaluateDataValue {
78 fn clone(&self) -> Self {
79 let mut frames: Vec<CloneFrame<'_>> = Vec::new();
80 let mut step = CloneStep::Descend(self);
81 loop {
82 step = match step {
83 CloneStep::Descend(value) => match value {
84 EvaluateDataValue::Literal(s) => {
85 CloneStep::Deliver(EvaluateDataValue::Literal(s.clone()))
86 }
87 EvaluateDataValue::Boolean(b) => {
88 CloneStep::Deliver(EvaluateDataValue::Boolean(*b))
89 }
90 EvaluateDataValue::List(items) => {
91 frames.push(CloneFrame::List {
92 out: Vec::with_capacity(items.len()),
93 children: items.iter(),
94 });
95 CloneStep::NextChild
96 }
97 EvaluateDataValue::Map(map) => {
98 frames.push(CloneFrame::Map {
99 out: IndexMap::with_capacity(map.len()),
100 pending_key: None,
101 children: map.iter(),
102 });
103 CloneStep::NextChild
104 }
105 },
106 CloneStep::NextChild => {
107 let frame = frames
108 .last_mut()
109 .expect("BUG: clone frame stack empty while reading a child");
110 match frame.next_child() {
111 Some(child) => CloneStep::Descend(child),
112 None => {
113 let frame = frames
114 .pop()
115 .expect("BUG: clone frame stack empty on completion");
116 CloneStep::Deliver(frame.finish())
117 }
118 }
119 }
120 CloneStep::Deliver(value) => match frames.last_mut() {
121 None => return value,
122 Some(frame) => {
123 frame.push(value);
124 CloneStep::NextChild
125 }
126 },
127 };
128 }
129 }
130}
131
132enum CloneStep<'a> {
133 Descend(&'a EvaluateDataValue),
134 NextChild,
135 Deliver(EvaluateDataValue),
136}
137
138enum CloneFrame<'a> {
139 List {
140 out: Vec<EvaluateDataValue>,
141 children: std::slice::Iter<'a, EvaluateDataValue>,
142 },
143 Map {
144 out: IndexMap<String, EvaluateDataValue>,
145 pending_key: Option<String>,
146 children: indexmap::map::Iter<'a, String, EvaluateDataValue>,
147 },
148}
149
150impl<'a> CloneFrame<'a> {
151 fn next_child(&mut self) -> Option<&'a EvaluateDataValue> {
152 match self {
153 CloneFrame::List { children, .. } => children.next(),
154 CloneFrame::Map {
155 pending_key,
156 children,
157 ..
158 } => children.next().map(|(key, child)| {
159 *pending_key = Some(key.clone());
160 child
161 }),
162 }
163 }
164
165 fn push(&mut self, value: EvaluateDataValue) {
166 match self {
167 CloneFrame::List { out, .. } => out.push(value),
168 CloneFrame::Map {
169 out, pending_key, ..
170 } => {
171 let key = pending_key
172 .take()
173 .expect("BUG: clone map child delivered without its key");
174 out.insert(key, value);
175 }
176 }
177 }
178
179 fn finish(self) -> EvaluateDataValue {
180 match self {
181 CloneFrame::List { out, .. } => EvaluateDataValue::List(out),
182 CloneFrame::Map { out, .. } => EvaluateDataValue::Map(out),
183 }
184 }
185}
186
187impl PartialEq for EvaluateDataValue {
188 fn eq(&self, other: &Self) -> bool {
189 let mut pending: Vec<(&EvaluateDataValue, &EvaluateDataValue)> = vec![(self, other)];
190 while let Some((left, right)) = pending.pop() {
191 match (left, right) {
192 (EvaluateDataValue::Literal(a), EvaluateDataValue::Literal(b)) => {
193 if a != b {
194 return false;
195 }
196 }
197 (EvaluateDataValue::Boolean(a), EvaluateDataValue::Boolean(b)) => {
198 if a != b {
199 return false;
200 }
201 }
202 (EvaluateDataValue::List(a), EvaluateDataValue::List(b)) => {
203 if a.len() != b.len() {
204 return false;
205 }
206 pending.extend(a.iter().zip(b.iter()));
207 }
208 (EvaluateDataValue::Map(a), EvaluateDataValue::Map(b)) => {
209 if a.len() != b.len() {
210 return false;
211 }
212 for (key, value) in a {
213 match b.get(key) {
214 Some(other_value) => pending.push((value, other_value)),
215 None => return false,
216 }
217 }
218 }
219 _ => return false,
220 }
221 }
222 true
223 }
224}
225
226impl Eq for EvaluateDataValue {}
227
228enum DebugTask<'a> {
229 Node(&'a EvaluateDataValue, usize),
230 Text(&'static str),
231 Quoted(&'a str),
232 Indent(usize),
233}
234
235impl std::fmt::Debug for EvaluateDataValue {
236 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
237 let pretty = f.alternate();
238 let mut tasks = vec![DebugTask::Node(self, 0)];
239 while let Some(task) = tasks.pop() {
240 match task {
241 DebugTask::Node(value, level) => {
242 let mut seq = Vec::new();
243 debug_seq(value, level, pretty, &mut seq);
244 tasks.extend(seq.into_iter().rev());
245 }
246 DebugTask::Text(text) => f.write_str(text)?,
247 DebugTask::Quoted(text) => write!(f, "{text:?}")?,
248 DebugTask::Indent(level) => write!(f, "{:width$}", "", width = 4 * level)?,
249 }
250 }
251 Ok(())
252 }
253}
254
255fn debug_seq<'a>(
257 value: &'a EvaluateDataValue,
258 level: usize,
259 pretty: bool,
260 seq: &mut Vec<DebugTask<'a>>,
261) {
262 match value {
263 EvaluateDataValue::Literal(text) => debug_tuple(seq, level, pretty, "Literal", |out, _| {
264 out.push(DebugTask::Quoted(text))
265 }),
266 EvaluateDataValue::Boolean(b) => debug_tuple(seq, level, pretty, "Boolean", |out, _| {
267 out.push(DebugTask::Text(if *b { "true" } else { "false" }))
268 }),
269 EvaluateDataValue::Map(map) => {
270 debug_tuple(seq, level, pretty, "Map", |out, field_level| {
271 if map.is_empty() {
272 out.push(DebugTask::Text("{}"));
273 } else if pretty {
274 out.push(DebugTask::Text("{\n"));
275 for (key, child) in map {
276 out.push(DebugTask::Indent(field_level + 1));
277 out.push(DebugTask::Quoted(key));
278 out.push(DebugTask::Text(": "));
279 out.push(DebugTask::Node(child, field_level + 1));
280 out.push(DebugTask::Text(",\n"));
281 }
282 out.push(DebugTask::Indent(field_level));
283 out.push(DebugTask::Text("}"));
284 } else {
285 out.push(DebugTask::Text("{"));
286 for (index, (key, child)) in map.iter().enumerate() {
287 if index > 0 {
288 out.push(DebugTask::Text(", "));
289 }
290 out.push(DebugTask::Quoted(key));
291 out.push(DebugTask::Text(": "));
292 out.push(DebugTask::Node(child, field_level));
293 }
294 out.push(DebugTask::Text("}"));
295 }
296 })
297 }
298 EvaluateDataValue::List(items) => {
299 debug_tuple(seq, level, pretty, "List", |out, field_level| {
300 if items.is_empty() {
301 out.push(DebugTask::Text("[]"));
302 } else if pretty {
303 out.push(DebugTask::Text("[\n"));
304 for child in items {
305 out.push(DebugTask::Indent(field_level + 1));
306 out.push(DebugTask::Node(child, field_level + 1));
307 out.push(DebugTask::Text(",\n"));
308 }
309 out.push(DebugTask::Indent(field_level));
310 out.push(DebugTask::Text("]"));
311 } else {
312 out.push(DebugTask::Text("["));
313 for (index, child) in items.iter().enumerate() {
314 if index > 0 {
315 out.push(DebugTask::Text(", "));
316 }
317 out.push(DebugTask::Node(child, field_level));
318 }
319 out.push(DebugTask::Text("]"));
320 }
321 })
322 }
323 }
324}
325
326fn debug_tuple<'a>(
328 seq: &mut Vec<DebugTask<'a>>,
329 level: usize,
330 pretty: bool,
331 name: &'static str,
332 field: impl FnOnce(&mut Vec<DebugTask<'a>>, usize),
333) {
334 seq.push(DebugTask::Text(name));
335 if pretty {
336 seq.push(DebugTask::Text("(\n"));
337 seq.push(DebugTask::Indent(level + 1));
338 field(seq, level + 1);
339 seq.push(DebugTask::Text(",\n"));
340 seq.push(DebugTask::Indent(level));
341 seq.push(DebugTask::Text(")"));
342 } else {
343 seq.push(DebugTask::Text("("));
344 field(seq, level);
345 seq.push(DebugTask::Text(")"));
346 }
347}
348
349impl From<bool> for EvaluateDataValue {
350 fn from(value: bool) -> Self {
351 Self::Boolean(value)
352 }
353}
354
355impl From<String> for EvaluateDataValue {
356 fn from(value: String) -> Self {
357 Self::Literal(value)
358 }
359}
360
361impl From<&str> for EvaluateDataValue {
362 fn from(value: &str) -> Self {
363 Self::Literal(value.to_string())
364 }
365}
366
367fn reject_case_insensitive_duplicates<'a>(
368 keys: impl IntoIterator<Item = &'a str>,
369 describe: impl Fn(&str) -> String,
370) -> Result<(), Error> {
371 let mut seen = HashSet::new();
372 for key in keys {
373 let canonical = ascii_lowercase_logical_name(key.to_string());
374 if !seen.insert(canonical.clone()) {
375 return Err(Error::request(
376 describe(&canonical),
377 Some("Keys are case-insensitive; remove the duplicate"),
378 ));
379 }
380 }
381 Ok(())
382}
383
384pub fn evaluate_data_from_entries(
386 entries: Vec<(String, EvaluateDataValue)>,
387) -> Result<IndexMap<String, EvaluateDataValue>, Error> {
388 reject_case_insensitive_duplicates(entries.iter().map(|(k, _)| k.as_str()), |k| {
389 format!("Duplicate data key '{k}'")
390 })?;
391 Ok(entries.into_iter().collect())
392}
393
394pub fn evaluate_data_map_from_entries(
396 entries: Vec<(String, EvaluateDataValue)>,
397) -> Result<EvaluateDataValue, Error> {
398 reject_case_insensitive_duplicates(entries.iter().map(|(k, _)| k.as_str()), |k| {
399 format!("Duplicate object key '{k}'")
400 })?;
401 Ok(EvaluateDataValue::Map(entries.into_iter().collect()))
402}
403
404pub fn evaluate_data_null_list_item(position: usize) -> Error {
408 Error::request(
409 format!("list item {position} is null"),
410 Some("remove the item or supply a value"),
411 )
412}
413
414pub fn attribute_evaluate_data_error(error: Error, key: &str) -> Error {
416 if error.related_data().is_some() {
417 error
418 } else {
419 error.with_related_data(key)
420 }
421}
422
423pub fn parse_data_value(
424 input: &EvaluateDataValue,
425 lemma_type: &Arc<LemmaType>,
426 source: &Source,
427 unit_index: &UnitIndex,
428 named_types: &IndexMap<String, Arc<LemmaType>>,
429) -> Result<TypedLiteral, Error> {
430 let mut frames: Vec<ListParseFrame<'_>> = Vec::new();
431 let mut step = ParseStep::Parse(input, Arc::clone(lemma_type));
432 loop {
433 step = match step {
434 ParseStep::Parse(value, ty) => {
435 match parse_node(value, &ty, source, unit_index, named_types)? {
436 ParseNode::Literal(literal) => ParseStep::Deliver(literal),
437 ParseNode::List {
438 rows,
439 lemma_type,
440 element_type,
441 } => {
442 frames.push(ListParseFrame {
443 rows: rows.iter(),
444 lemma_type,
445 element_type,
446 converted: Vec::with_capacity(rows.len()),
447 });
448 ParseStep::NextChild
449 }
450 }
451 }
452 ParseStep::NextChild => {
453 let frame = frames
454 .last_mut()
455 .expect("BUG: list parse frame stack empty while reading a child");
456 match frame.rows.next() {
457 Some(child) => ParseStep::Parse(child, Arc::clone(&frame.element_type)),
458 None => {
459 let frame = frames
460 .pop()
461 .expect("BUG: list parse frame stack empty on completion");
462 ParseStep::Deliver(TypedLiteral {
463 value: ValueKind::List(frame.converted.into()),
464 lemma_type: frame.lemma_type,
465 })
466 }
467 }
468 }
469 ParseStep::Deliver(literal) => match frames.last_mut() {
470 None => return Ok(literal),
471 Some(frame) => {
472 frame.converted.push(literal);
473 ParseStep::NextChild
474 }
475 },
476 };
477 }
478}
479
480enum ParseStep<'a> {
481 Parse(&'a EvaluateDataValue, Arc<LemmaType>),
482 NextChild,
483 Deliver(TypedLiteral),
484}
485
486struct ListParseFrame<'a> {
487 rows: std::slice::Iter<'a, EvaluateDataValue>,
488 lemma_type: Arc<LemmaType>,
489 element_type: Arc<LemmaType>,
490 converted: Vec<TypedLiteral>,
491}
492
493enum ParseNode<'a> {
494 Literal(TypedLiteral),
495 List {
496 rows: &'a [EvaluateDataValue],
497 lemma_type: Arc<LemmaType>,
498 element_type: Arc<LemmaType>,
499 },
500}
501
502fn parse_node<'a>(
505 input: &'a EvaluateDataValue,
506 lemma_type: &Arc<LemmaType>,
507 source: &Source,
508 unit_index: &UnitIndex,
509 named_types: &IndexMap<String, Arc<LemmaType>>,
510) -> Result<ParseNode<'a>, Error> {
511 let type_spec = &lemma_type.specifications;
512
513 let (kind, binding_unit) = match (input, type_spec) {
514 (EvaluateDataValue::Literal(s), _) => {
515 return parse_literal(s, lemma_type, source, unit_index, named_types)
516 .map(ParseNode::Literal);
517 }
518 (EvaluateDataValue::Boolean(b), TypeSpecification::Boolean { .. }) => {
519 (ValueKind::Boolean(*b), None)
520 }
521 (EvaluateDataValue::Boolean(_), _) => {
522 panic!("BUG: boolean overlay on non-boolean type is a request shape error, not parse")
523 }
524 (EvaluateDataValue::Map(map), TypeSpecification::Measure { .. })
525 | (EvaluateDataValue::Map(map), TypeSpecification::Ratio { .. }) => {
526 let (magnitude, unit) = single_unit_literal_map(map).expect(
527 "BUG: measure/ratio map overlay is a request shape error unless it is a single-unit Literal",
528 );
529 return parse_literal(
530 &format!("{magnitude} {unit}"),
531 lemma_type,
532 source,
533 unit_index,
534 named_types,
535 )
536 .map(ParseNode::Literal);
537 }
538 (EvaluateDataValue::Map(_), _) => {
539 panic!("BUG: map overlay on this type is a request shape error, not parse")
540 }
541 (EvaluateDataValue::List(rows), TypeSpecification::List { .. }) => {
542 return Ok(ParseNode::List {
543 rows: rows.as_slice(),
544 lemma_type: Arc::clone(lemma_type),
545 element_type: Arc::clone(lemma_type.list_element_lemma_type()),
546 });
547 }
548 (EvaluateDataValue::List(_), _) => {
549 panic!("BUG: list overlay on this type is a request shape error, not parse")
550 }
551 };
552
553 Ok(ParseNode::Literal(typed_literal_with_binding_unit(
554 kind,
555 binding_unit,
556 lemma_type,
557 )))
558}
559
560fn parse_literal(
561 text: &str,
562 lemma_type: &Arc<LemmaType>,
563 source: &Source,
564 unit_index: &UnitIndex,
565 named_types: &IndexMap<String, Arc<LemmaType>>,
566) -> Result<TypedLiteral, Error> {
567 let parsed = parse_value_from_string(text, &lemma_type.specifications, source)?;
568 typed_literal_from_parsed(&parsed, lemma_type, unit_index, named_types)
569 .map_err(|msg| Error::validation(msg, Some(source.clone()), None::<String>))
570}
571
572pub(crate) fn typed_literal_from_parsed(
574 parsed: &Value,
575 lemma_type: &Arc<LemmaType>,
576 unit_index: &UnitIndex,
577 named_types: &IndexMap<String, Arc<LemmaType>>,
578) -> Result<TypedLiteral, String> {
579 let resolve = UnitTokenResolver::Typed {
580 unit_index,
581 named_types,
582 lemma_type: lemma_type.as_ref(),
583 };
584 let kind = parser_value_to_value_kind(parsed, lemma_type.as_ref(), Some(&resolve))?;
585 let binding = match binding_unit_from_parser_value(parsed) {
586 Some(written) => Some(resolve.resolve(&written)?),
587 None => None,
588 };
589 Ok(typed_literal_with_binding_unit(kind, binding, lemma_type))
590}
591
592fn typed_literal_with_binding_unit(
593 kind: ValueKind,
594 binding_unit: Option<String>,
595 lemma_type: &Arc<LemmaType>,
596) -> TypedLiteral {
597 let typed_type = match binding_unit {
598 Some(unit)
599 if matches!(
600 &lemma_type.specifications,
601 TypeSpecification::Measure { .. } | TypeSpecification::Ratio { .. }
602 ) =>
603 {
604 Arc::new(lemma_type.as_ref().clone().with_unit(unit))
605 }
606 Some(_) | None => Arc::clone(lemma_type),
607 };
608 TypedLiteral {
609 value: kind,
610 lemma_type: typed_type,
611 }
612}
613
614fn binding_unit_from_parser_value(value: &Value) -> Option<String> {
615 use crate::parsing::ast::Value;
616 match value {
617 Value::NumberWithUnit(_, unit) => Some(unit.clone()),
618 Value::Range(left, right) => match (left.as_ref(), right.as_ref()) {
619 (Value::NumberWithUnit(_, left_unit), Value::NumberWithUnit(_, right_unit))
620 if left_unit == right_unit =>
621 {
622 Some(left_unit.clone())
623 }
624 _ => None,
625 },
626 _ => None,
627 }
628}
629
630fn single_unit_literal_map(map: &IndexMap<String, EvaluateDataValue>) -> Option<(&str, &str)> {
636 if map.len() != 1 {
637 return None;
638 }
639 let (unit, value) = map.iter().next()?;
640 match value {
641 EvaluateDataValue::Literal(magnitude) => Some((magnitude.as_str(), unit.as_str())),
642 _ => None,
643 }
644}
645
646enum ShapeCheck<'a> {
647 Accepts,
648 Mismatch(String),
649 Rows {
650 rows: &'a [EvaluateDataValue],
651 element: &'a LemmaType,
652 },
653}
654
655fn shape_check<'a>(input: &'a EvaluateDataValue, lemma_type: &'a LemmaType) -> ShapeCheck<'a> {
656 let type_spec = &lemma_type.specifications;
657 match (input, type_spec) {
658 (EvaluateDataValue::Literal(_), TypeSpecification::List { .. }) => ShapeCheck::Mismatch(
659 format!("literal overlay is not valid for list data ({type_spec})"),
660 ),
661 (EvaluateDataValue::Literal(_), _) => ShapeCheck::Accepts,
662 (EvaluateDataValue::Boolean(_), TypeSpecification::Boolean { .. }) => ShapeCheck::Accepts,
663 (EvaluateDataValue::Boolean(_), _) => ShapeCheck::Mismatch(format!(
664 "boolean input is only valid for boolean data, not {type_spec}"
665 )),
666 (
667 EvaluateDataValue::Map(map),
668 TypeSpecification::Measure { .. } | TypeSpecification::Ratio { .. },
669 ) => {
670 if single_unit_literal_map(map).is_some() {
671 ShapeCheck::Accepts
672 } else {
673 ShapeCheck::Mismatch(
674 "measure and ratio data must be a convenience string or a single-unit map"
675 .to_string(),
676 )
677 }
678 }
679 (EvaluateDataValue::Map(_), _) => ShapeCheck::Mismatch(format!(
680 "object overlay is only valid for measure or ratio data, not {type_spec}"
681 )),
682 (EvaluateDataValue::List(rows), TypeSpecification::List { .. }) => ShapeCheck::Rows {
683 rows: rows.as_slice(),
684 element: lemma_type.list_element_lemma_type().as_ref(),
685 },
686 (EvaluateDataValue::List(_), _) => ShapeCheck::Mismatch(format!(
687 "array overlay is not valid for scalar data ({type_spec})"
688 )),
689 }
690}
691
692struct ShapeFrame<'a> {
693 rows: std::slice::Iter<'a, EvaluateDataValue>,
694 element: &'a LemmaType,
695 position: usize,
696}
697
698fn evaluate_data_shape_error(input: &EvaluateDataValue, lemma_type: &LemmaType) -> Option<String> {
701 let mut frames: Vec<ShapeFrame<'_>> = Vec::new();
702 let mut pending = Some((input, lemma_type));
703 loop {
704 if let Some((value, ty)) = pending.take() {
705 match shape_check(value, ty) {
706 ShapeCheck::Accepts => {}
707 ShapeCheck::Mismatch(message) => return Some(item_path_message(&frames, message)),
708 ShapeCheck::Rows { rows, element } => frames.push(ShapeFrame {
709 rows: rows.iter(),
710 element,
711 position: 0,
712 }),
713 }
714 }
715 let frame = frames.last_mut()?;
716 match frame.rows.next() {
717 Some(child) => {
718 frame.position += 1;
719 pending = Some((child, frame.element));
720 }
721 None => {
722 frames.pop();
723 }
724 }
725 }
726}
727
728fn item_path_message(frames: &[ShapeFrame<'_>], message: String) -> String {
729 if frames.is_empty() {
730 return message;
731 }
732 let positions: Vec<String> = frames.iter().map(|f| f.position.to_string()).collect();
733 format!("list item {}: {message}", positions.join(", item "))
734}
735
736#[derive(Debug, Clone, Default)]
739pub(crate) struct RowOverrides {
740 pub(crate) fields: IndexMap<InstanceKey, IndexMap<usize, OperationResult>>,
741 pub(crate) keyed_lists: HashSet<(Option<InstanceKey>, DataPath)>,
744}
745
746impl RowOverrides {
747 fn extend(&mut self, other: RowOverrides) {
748 for (key, fields) in other.fields {
749 self.fields.entry(key).or_default().extend(fields);
750 }
751 self.keyed_lists.extend(other.keyed_lists);
752 }
753
754 fn under(self, owner: &InstanceKey) -> RowOverrides {
756 RowOverrides {
757 fields: self
758 .fields
759 .into_iter()
760 .map(|(key, fields)| (key.under(owner), fields))
761 .collect(),
762 keyed_lists: self
763 .keyed_lists
764 .into_iter()
765 .map(|(list_owner, path)| {
766 let list_owner = match list_owner {
767 Some(key) => key.under(owner),
768 None => owner.clone(),
769 };
770 (Some(list_owner), path)
771 })
772 .collect(),
773 }
774 }
775}
776
777#[derive(Debug, Clone, Default)]
783pub struct EvaluateData {
784 pub bindings: IndexMap<usize, OperationResult>,
787 pub(crate) row_overrides: RowOverrides,
790 pub ignored_unknown: Vec<String>,
792}
793
794impl EvaluateData {
795 pub fn resolve(
801 plan: &ExecutionPlan,
802 raw_values: IndexMap<String, EvaluateDataValue>,
803 limits: &ResourceLimits,
804 ) -> Result<Self, Error> {
805 Self::resolve_level(plan, raw_values, limits, ListLevel::Top)
806 }
807
808 fn resolve_level(
809 plan: &ExecutionPlan,
810 raw_values: IndexMap<String, EvaluateDataValue>,
811 limits: &ResourceLimits,
812 level: ListLevel,
813 ) -> Result<Self, Error> {
814 if level == ListLevel::Top {
815 reject_whole_list_with_row_keys(plan, &raw_values)?;
816 }
817 match run_resolve(ResolveFrame::Level(LevelFrame::new(
818 plan, None, raw_values, limits, level,
819 )))? {
820 ResolveOutput::Level(evaluate_data) => Ok(evaluate_data),
821 ResolveOutput::Binding(..) => {
822 panic!("BUG: root level frame returned a list binding")
823 }
824 }
825 }
826}
827
828enum ResolveOutput {
829 Level(EvaluateData),
830 Binding(OperationResult, RowOverrides),
832}
833
834enum ResolveStep<'a> {
835 Call(ResolveFrame<'a>),
836 Return(ResolveOutput),
837}
838
839enum ResolveFrame<'a> {
840 Level(LevelFrame<'a>),
841 List(ListFrame<'a>),
842}
843
844impl<'a> ResolveFrame<'a> {
845 fn resume(&mut self, input: Option<ResolveOutput>) -> Result<ResolveStep<'a>, Error> {
846 match self {
847 ResolveFrame::Level(frame) => frame.resume(input),
848 ResolveFrame::List(frame) => frame.resume(input),
849 }
850 }
851}
852
853fn run_resolve(root: ResolveFrame<'_>) -> Result<ResolveOutput, Error> {
854 let mut frames = vec![root];
855 let mut input = None;
856 loop {
857 let frame = frames.last_mut().expect("BUG: resolve frame stack empty");
858 match frame.resume(input.take())? {
859 ResolveStep::Call(child) => frames.push(child),
860 ResolveStep::Return(output) => {
861 frames.pop();
862 if frames.is_empty() {
863 return Ok(output);
864 }
865 input = Some(output);
866 }
867 }
868 }
869}
870
871struct LevelFrame<'a> {
874 plan: &'a ExecutionPlan,
875 row_key: Option<Arc<str>>,
878 limits: &'a ResourceLimits,
879 level: ListLevel,
880 entries: indexmap::map::IntoIter<String, EvaluateDataValue>,
881 seen_canonical: HashSet<String>,
882 evaluate_data: EvaluateData,
883 awaiting: Option<AwaitBinding>,
884}
885
886enum AwaitBinding {
889 Data { slot: usize },
890 RowField { key: InstanceKey, slot: usize },
891}
892
893impl<'a> LevelFrame<'a> {
894 fn new(
895 plan: &'a ExecutionPlan,
896 row_key: Option<Arc<str>>,
897 raw_values: IndexMap<String, EvaluateDataValue>,
898 limits: &'a ResourceLimits,
899 level: ListLevel,
900 ) -> Self {
901 let capacity = raw_values.len();
902 Self {
903 plan,
904 row_key,
905 limits,
906 level,
907 entries: raw_values.into_iter(),
908 seen_canonical: HashSet::with_capacity(capacity),
909 evaluate_data: EvaluateData {
910 bindings: IndexMap::with_capacity(capacity),
911 ..EvaluateData::default()
912 },
913 awaiting: None,
914 }
915 }
916
917 fn resume(&mut self, input: Option<ResolveOutput>) -> Result<ResolveStep<'a>, Error> {
918 match (self.awaiting.take(), input) {
919 (None, None) => {}
920 (
921 Some(AwaitBinding::Data { slot }),
922 Some(ResolveOutput::Binding(binding, overrides)),
923 ) => {
924 self.evaluate_data.bindings.insert(slot, binding);
925 self.evaluate_data.row_overrides.extend(overrides);
926 }
927 (
928 Some(AwaitBinding::RowField { key, slot }),
929 Some(ResolveOutput::Binding(binding, overrides)),
930 ) => {
931 self.evaluate_data
932 .row_overrides
933 .fields
934 .entry(key)
935 .or_default()
936 .insert(slot, binding);
937 self.evaluate_data.row_overrides.extend(overrides);
938 }
939 _ => panic!("BUG: level frame resumed with an unexpected output"),
940 }
941 self.advance()
942 }
943
944 fn advance(&mut self) -> Result<ResolveStep<'a>, Error> {
945 let plan = self.plan;
946 let limits = self.limits;
947 let level = self.level;
948 let evaluate_data = &mut self.evaluate_data;
949 let seen_canonical = &mut self.seen_canonical;
950
951 for (name, raw_value) in self.entries.by_ref() {
952 let canonical = canonical_logical_name(&name);
953 if seen_canonical.contains(canonical.as_ref()) {
954 let canonical = canonical.into_owned();
955 return Err(Error::request(
956 format!("Duplicate data key '{canonical}'"),
957 Some("Data keys are case-insensitive; remove the duplicate"),
958 )
959 .with_related_data(name));
960 }
961 seen_canonical.insert(canonical.to_string());
962
963 let Some((slot, data_path, data_definition)) =
964 resolve_evaluate_data_target(plan, &canonical)
965 else {
966 let row_field = match resolve_row_field_key(plan, &canonical) {
967 RowKeyLookup::Field(row_field) => row_field,
968 RowKeyLookup::Unknown => {
969 evaluate_data.ignored_unknown.push(name);
970 continue;
971 }
972 RowKeyLookup::Invalid(error) => return Err(error.with_related_data(name)),
973 };
974 if matches!(row_field.definition, DataDefinition::Import { .. }) {
975 if matches!(
976 raw_value,
977 EvaluateDataValue::Map(_) | EvaluateDataValue::List(_)
978 ) {
979 return Err(Error::request(
980 format!(
981 "data '{name}': '{name}' is a uses alias; objects and arrays are not a nesting level"
982 ),
983 Some(format!("bind fields with dotted keys, e.g. '{name}.<field>'")),
984 )
985 .with_related_data(name));
986 }
987 evaluate_data.ignored_unknown.push(name);
988 continue;
989 }
990 if row_field.sealed {
991 return Err(Error::request(
992 format!(
993 "data '{name}' is sealed by `uses -> with` and cannot be supplied at evaluate"
994 ),
995 Some("remove this key, or change the `uses` binding if the value should be caller-owned data"),
996 )
997 .with_related_data(name));
998 }
999 if let Some(list) = row_field.keyed_list {
1000 evaluate_data.row_overrides.keyed_lists.insert(list);
1001 }
1002 if matches!(row_field.definition, DataDefinition::InstanceList { .. }) {
1003 self.awaiting = Some(AwaitBinding::RowField {
1004 key: row_field.key,
1005 slot: row_field.slot,
1006 });
1007 return Ok(ResolveStep::Call(ResolveFrame::List(ListFrame {
1008 plan: row_field.template,
1009 path: row_field.field,
1010 list_key: canonical.into_owned(),
1011 under: self.row_key.clone(),
1012 name,
1013 overrides: RowOverrides::default(),
1014 limits,
1015 level,
1016 raw_value: Some(raw_value),
1017 rows: None,
1018 })));
1019 }
1020 let type_arc = Arc::clone(
1021 row_field
1022 .definition
1023 .resolved_type_arc()
1024 .expect("BUG: scalar row field is typed template data"),
1025 );
1026 let binding = resolve_scalar_binding(
1027 plan,
1028 &name,
1029 &canonical,
1030 &raw_value,
1031 &type_arc,
1032 row_field.definition.source(),
1033 limits,
1034 )?;
1035 evaluate_data
1036 .row_overrides
1037 .fields
1038 .entry(row_field.key)
1039 .or_default()
1040 .insert(row_field.slot, binding);
1041 continue;
1042 };
1043
1044 if data_definition.is_bound() {
1045 return Err(Error::request(
1046 format!(
1047 "data '{name}' is sealed by `uses -> with` and cannot be supplied at evaluate"
1048 ),
1049 Some("remove this key, or change the `uses` binding if the value should be caller-owned data"),
1050 )
1051 .with_related_data(name));
1052 }
1053
1054 let data_source = data_definition.source();
1055 let type_arc = match data_definition {
1056 DataDefinition::TypeDeclaration { resolved_type, .. }
1057 | DataDefinition::Value { resolved_type, .. }
1058 | DataDefinition::Reference { resolved_type, .. } => Arc::clone(resolved_type),
1059 DataDefinition::Import { .. } => {
1060 if matches!(
1061 raw_value,
1062 EvaluateDataValue::Map(_) | EvaluateDataValue::List(_)
1063 ) {
1064 return Err(Error::request(
1065 format!(
1066 "data '{name}': '{name}' is a uses alias; objects and arrays are not a nesting level"
1067 ),
1068 Some(format!("bind fields with dotted keys, e.g. '{name}.<field>'")),
1069 )
1070 .with_related_data(name));
1071 }
1072 evaluate_data.ignored_unknown.push(name);
1073 continue;
1074 }
1075 DataDefinition::InstanceList { .. } => {
1076 self.awaiting = Some(AwaitBinding::Data { slot });
1077 return Ok(ResolveStep::Call(ResolveFrame::List(ListFrame {
1078 plan,
1079 path: data_path,
1080 list_key: data_path.input_key(),
1081 under: self.row_key.clone(),
1082 name,
1083 overrides: RowOverrides::default(),
1084 limits,
1085 level,
1086 raw_value: Some(raw_value),
1087 rows: None,
1088 })));
1089 }
1090 };
1091
1092 let binding = resolve_scalar_binding(
1093 plan,
1094 &name,
1095 &canonical,
1096 &raw_value,
1097 &type_arc,
1098 data_source,
1099 limits,
1100 )?;
1101 evaluate_data.bindings.insert(slot, binding);
1102 }
1103
1104 Ok(ResolveStep::Return(ResolveOutput::Level(std::mem::take(
1105 evaluate_data,
1106 ))))
1107 }
1108}
1109
1110fn resolve_scalar_binding(
1114 plan: &ExecutionPlan,
1115 name: &str,
1116 input_key: &str,
1117 raw_value: &EvaluateDataValue,
1118 type_arc: &Arc<LemmaType>,
1119 data_source: &Source,
1120 limits: &ResourceLimits,
1121) -> Result<OperationResult, Error> {
1122 if raw_value.is_empty() && type_arc.empty_runtime_input_vetoes() {
1123 return Ok(OperationResult::Veto(VetoType::computation(format!(
1124 "Data {} cannot be empty.",
1125 type_arc.label_for_data_input(input_key)
1126 ))));
1127 }
1128
1129 if let Some(message) = evaluate_data_shape_error(raw_value, type_arc) {
1130 return Err(Error::request(
1131 format!("data '{name}': {message}"),
1132 Some(format!("fix the overlay for '{name}'")),
1133 )
1134 .with_related_data(name));
1135 }
1136
1137 let typed = match parse_data_value(
1138 raw_value,
1139 type_arc,
1140 data_source,
1141 plan.expression_unit_index(),
1142 &plan.resolved_types.resolved,
1143 ) {
1144 Ok(value) => value,
1145 Err(error) => {
1146 return Ok(OperationResult::Veto(VetoType::computation(
1147 type_arc.data_veto_message(input_key, error.message()),
1148 )));
1149 }
1150 };
1151
1152 if let ValueKind::List(rows) = &typed.value {
1153 if rows.len() > limits.max_list_rows {
1154 return Err(Error::request(
1155 format!(
1156 "data '{name}' has {} rows, over max_list_rows {}",
1157 rows.len(),
1158 limits.max_list_rows
1159 ),
1160 Some("reduce the list size or raise max_list_rows".to_string()),
1161 )
1162 .with_related_data(name));
1163 }
1164 }
1165
1166 let literal_value = LiteralValue {
1167 value: typed.value.clone(),
1168 };
1169 let oversize = match &typed.value {
1170 ValueKind::List(items) => items
1171 .iter()
1172 .position(|item| item.byte_size() > limits.max_data_value_bytes)
1173 .map(|index| {
1174 format!(
1175 "Data {} list item {} exceeds the size limit.",
1176 type_arc.label_for_data_input(input_key),
1177 index + 1
1178 )
1179 }),
1180 _ => (literal_value.byte_size() > limits.max_data_value_bytes).then(|| {
1181 format!(
1182 "Data {} exceeds the size limit.",
1183 type_arc.label_for_data_input(input_key)
1184 )
1185 }),
1186 };
1187 if let Some(reason) = oversize {
1188 return Ok(OperationResult::Veto(VetoType::computation(reason)));
1189 }
1190
1191 if let Err(message) = validate_value_against_type(
1192 typed.lemma_type.as_ref(),
1193 &literal_value,
1194 plan.expression_unit_index(),
1195 ) {
1196 return Ok(OperationResult::Veto(VetoType::computation(
1197 type_arc.data_veto_message(input_key, &message),
1198 )));
1199 }
1200
1201 Ok(OperationResult::from_bound(BoundValueKind {
1202 value: typed.value.clone(),
1203 unit: typed.lemma_type.unit().map(std::sync::Arc::from),
1204 }))
1205}
1206
1207fn resolve_evaluate_data_target<'a>(
1211 plan: &'a ExecutionPlan,
1212 key: &str,
1213) -> Option<(usize, &'a DataPath, &'a DataDefinition)> {
1214 let path = plan.input_key_index.get(key)?;
1215 let (slot, path, definition) = plan
1216 .data
1217 .get_full(path)
1218 .unwrap_or_else(|| panic!("BUG: indexed input '{key}' has no data definition"));
1219 Some((slot, path, definition))
1220}
1221
1222type ListAddress = (Option<InstanceKey>, DataPath);
1224
1225struct RowFieldKey<'a> {
1228 key: InstanceKey,
1230 template: &'a ExecutionPlan,
1231 slot: usize,
1232 field: &'a DataPath,
1233 definition: &'a DataDefinition,
1234 sealed: bool,
1236 keyed_list: Option<ListAddress>,
1239 lists: Vec<(ListAddress, String)>,
1241}
1242
1243enum RowKeyLookup<'a> {
1244 Field(RowFieldKey<'a>),
1245 Unknown,
1247 Invalid(Error),
1250}
1251
1252fn resolve_row_field_key<'a>(plan: &'a ExecutionPlan, key: &str) -> RowKeyLookup<'a> {
1255 let parts: Vec<&str> = key.split('.').collect();
1256 let mut template = plan;
1257 let mut holder: Option<InstanceKey> = None;
1258 let mut keyed: Option<(ListAddress, &'a ListKey)> = None;
1259 let mut lists: Vec<(ListAddress, String)> = Vec::new();
1260 let mut index = 0;
1261 loop {
1262 let Some(path) = template.input_key_index.get(parts[index]) else {
1263 return unknown_under_list(key, &lists);
1264 };
1265 let (slot, _, definition) = template
1266 .data
1267 .get_full(path)
1268 .expect("BUG: indexed input key has a data definition");
1269 let row = holder.as_ref().map(|key| key.row.as_ref());
1270 if index + 1 == parts.len() {
1271 if matches!(definition, DataDefinition::Import { .. }) && !lists.is_empty() {
1272 return unknown_under_list(key, &lists);
1273 }
1274 let Some(key) = holder else {
1275 return RowKeyLookup::Unknown;
1276 };
1277 let keyed_list = keyed.and_then(|(list, list_key)| match list_key {
1278 ListKey::Fields(fields) => fields
1279 .iter()
1280 .any(|field| field.path == *path)
1281 .then_some(list),
1282 ListKey::Numbered => None,
1283 });
1284 return RowKeyLookup::Field(RowFieldKey {
1285 sealed: key.row.bindings[slot].is_some(),
1286 key,
1287 template,
1288 slot,
1289 field: path,
1290 definition,
1291 keyed_list,
1292 lists,
1293 });
1294 }
1295 match definition {
1296 DataDefinition::Import { .. } => {
1297 let import = Arc::clone(template.import_row(row, path, None));
1298 template = template.row_template(&import.identity);
1299 holder = Some(InstanceKey::new(import, holder));
1300 keyed = None;
1301 index += 1;
1302 }
1303 DataDefinition::InstanceList { .. } => {
1304 let list_key = parts[..=index].join(".");
1305 let source = template
1306 .instance_lists
1307 .get(path)
1308 .unwrap_or_else(|| panic!("BUG: instance list '{list_key}' has no source"));
1309 let (rows, owner) = match row.and_then(|row| row.bindings[slot].as_ref()) {
1310 None => (source.store.rows.as_slice(), holder.clone()),
1311 Some(RowBinding::Rows { rows, hops, .. }) => {
1312 let holder = holder.as_ref().expect("BUG: a bound list is a row field");
1313 (rows.as_slice(), holder.scope(*hops).cloned())
1314 }
1315 Some(
1316 RowBinding::Bound(_)
1317 | RowBinding::Deferred { .. }
1318 | RowBinding::Instance(_),
1319 ) => {
1320 return RowKeyLookup::Invalid(Error::request(
1321 format!(
1322 "data '{key}' is sealed by `uses -> with` and cannot be supplied at evaluate"
1323 ),
1324 Some(format!(
1325 "'{list_key}' is bound by `uses -> with`; its rows take no run data"
1326 )),
1327 ));
1328 }
1329 };
1330 let rest = &parts[index + 1..];
1331 let number = match rest.first().map(|row| row.parse::<usize>()) {
1332 Some(Ok(number)) if rest.len() > 1 => number,
1333 _ => {
1334 return RowKeyLookup::Invalid(no_list_data(
1335 key,
1336 &list_key,
1337 format!("fields of a row of '{list_key}' are keyed '{list_key}.<row>.<field>'"),
1338 ));
1339 }
1340 };
1341 if number == 0 || number > rows.len() {
1342 return RowKeyLookup::Invalid(no_list_data(
1343 key,
1344 &list_key,
1345 format!(
1346 "'{list_key}' has {} spec-written row(s); rows are numbered from 1",
1347 rows.len()
1348 ),
1349 ));
1350 }
1351 let list = (owner.clone(), path.clone());
1352 lists.push((list.clone(), list_key));
1353 keyed = match row.and_then(|row| row.bindings[slot].as_ref()) {
1354 None => Some((list, &source.key)),
1355 Some(_) => None,
1356 };
1357 let listed = Arc::clone(&rows[number - 1]);
1358 template = template.row_template(&listed.identity);
1359 holder = Some(InstanceKey::new(listed, owner));
1360 index += 2;
1361 }
1362 DataDefinition::Value { .. }
1363 | DataDefinition::TypeDeclaration { .. }
1364 | DataDefinition::Reference { .. } => {
1365 return unknown_under_list(key, &lists);
1366 }
1367 }
1368 }
1369}
1370
1371pub(crate) fn row_field_definition<'a>(
1374 plan: &'a ExecutionPlan,
1375 key: &str,
1376) -> Option<(
1377 &'a ExecutionPlan,
1378 &'a DataDefinition,
1379 InstanceKey,
1380 &'a DataPath,
1381)> {
1382 match resolve_row_field_key(plan, key) {
1383 RowKeyLookup::Field(row_field) if row_field.lists.is_empty() => Some((
1384 row_field.template,
1385 row_field.definition,
1386 row_field.key,
1387 row_field.field,
1388 )),
1389 RowKeyLookup::Field(_) => None,
1390 RowKeyLookup::Unknown | RowKeyLookup::Invalid(_) => None,
1391 }
1392}
1393
1394fn unknown_under_list<'a>(key: &str, lists: &[(ListAddress, String)]) -> RowKeyLookup<'a> {
1397 match lists.last() {
1398 Some((_, list_key)) => {
1399 let field = key[list_key.len() + 1..]
1400 .split_once('.')
1401 .map_or("", |(_, field)| field);
1402 RowKeyLookup::Invalid(no_list_data(
1403 key,
1404 list_key,
1405 format!("rows of '{list_key}' have no field '{field}'"),
1406 ))
1407 }
1408 None => RowKeyLookup::Unknown,
1409 }
1410}
1411
1412fn no_list_data(key: &str, list_key: &str, hint: String) -> Error {
1413 Error::request(
1414 format!("data '{key}' names no data of '{list_key}'"),
1415 Some(hint),
1416 )
1417}
1418
1419fn reject_whole_list_with_row_keys(
1422 plan: &ExecutionPlan,
1423 raw_values: &IndexMap<String, EvaluateDataValue>,
1424) -> Result<(), Error> {
1425 let mut whole_lists: HashSet<ListAddress> = HashSet::new();
1426 let mut row_fields: Vec<(&String, Vec<(ListAddress, String)>)> = Vec::new();
1427 for name in raw_values.keys() {
1428 let canonical = canonical_logical_name(name);
1429 if let Some((_, path, definition)) = resolve_evaluate_data_target(plan, &canonical) {
1430 if matches!(definition, DataDefinition::InstanceList { .. }) {
1431 whole_lists.insert((None, path.clone()));
1432 }
1433 continue;
1434 }
1435 if let RowKeyLookup::Field(row_field) = resolve_row_field_key(plan, &canonical) {
1436 if matches!(row_field.definition, DataDefinition::InstanceList { .. }) {
1437 whole_lists.insert((Some(row_field.key.clone()), row_field.field.clone()));
1438 }
1439 row_fields.push((name, row_field.lists));
1440 }
1441 }
1442 for (name, lists) in row_fields {
1443 for (list, list_key) in lists {
1444 if whole_lists.contains(&list) {
1445 return Err(Error::request(
1446 format!(
1447 "data '{list_key}': cannot supply both the whole list and dotted row keys such as '{name}'"
1448 ),
1449 Some(format!(
1450 "pass either '{list_key}' as an array, or only dotted keys under '{list_key}'"
1451 )),
1452 )
1453 .with_related_data(list_key));
1454 }
1455 }
1456 }
1457 Ok(())
1458}
1459
1460#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1463enum ListLevel {
1464 Top,
1465 Nested,
1466}
1467
1468fn check_list_tree(
1473 template: &ExecutionPlan,
1474 raw_rows: &[EvaluateDataValue],
1475 limits: &ResourceLimits,
1476) -> Result<(), String> {
1477 let mut stack: Vec<(&ExecutionPlan, &EvaluateDataValue, usize)> =
1478 raw_rows.iter().map(|row| (template, row, 2)).collect();
1479 let mut nodes = 0usize;
1480 let mut deepest = 0usize;
1481 while let Some((row_template, raw_row, depth)) = stack.pop() {
1482 nodes += 1;
1483 if nodes > limits.max_list_nodes {
1484 return Err(format!(
1485 "has more than max_list_nodes {} rows across all levels",
1486 limits.max_list_nodes
1487 ));
1488 }
1489 deepest = deepest.max(depth);
1490 let EvaluateDataValue::Map(fields) = raw_row else {
1491 continue;
1492 };
1493 for (field, value) in fields {
1494 let EvaluateDataValue::List(rows) = value else {
1495 continue;
1496 };
1497 let Some(source) = row_template
1498 .input_key_index
1499 .get(canonical_logical_name(field).as_ref())
1500 .and_then(|path| row_template.instance_lists.get(path))
1501 else {
1502 continue;
1503 };
1504 let nested = row_template.row_template(&source.identity);
1505 stack.extend(rows.iter().map(|row| (nested, row, depth + 1)));
1506 }
1507 }
1508 if deepest > limits.max_list_depth {
1509 return Err(format!(
1510 "rows nest {deepest} levels deep counting the spec itself, over max_list_depth {}",
1511 limits.max_list_depth
1512 ));
1513 }
1514 Ok(())
1515}
1516
1517struct ListFrame<'a> {
1521 plan: &'a ExecutionPlan,
1522 path: &'a DataPath,
1523 list_key: String,
1525 under: Option<Arc<str>>,
1528 name: String,
1529 overrides: RowOverrides,
1531 limits: &'a ResourceLimits,
1532 level: ListLevel,
1533 raw_value: Option<EvaluateDataValue>,
1534 rows: Option<ListRows<'a>>,
1535}
1536
1537struct ListRows<'a> {
1538 source: &'a InstanceListSource,
1539 identity: Arc<SpecIdentity>,
1541 template: &'a ExecutionPlan,
1542 row_type: Arc<LemmaType>,
1543 raw_rows: std::vec::IntoIter<EvaluateDataValue>,
1544 number: usize,
1545 literals: Vec<TypedLiteral>,
1546 key_slots: Box<[KeySlot<'a>]>,
1548 keyed: Vec<Vec<TypedLiteral>>,
1549}
1550
1551struct KeySlot<'a> {
1552 field: &'a KeyField,
1553 slot: usize,
1554 field_type: Arc<LemmaType>,
1555}
1556
1557fn request_error(name: &str, message: String) -> Error {
1558 Error::request(format!("data '{name}': {message}"), None::<String>).with_related_data(name)
1559}
1560
1561impl<'a> ListFrame<'a> {
1562 fn resume(&mut self, input: Option<ResolveOutput>) -> Result<ResolveStep<'a>, Error> {
1563 match self.rows.take() {
1564 None => {
1565 assert!(
1566 input.is_none(),
1567 "BUG: list frame started with a child output"
1568 );
1569 let raw_value = self
1570 .raw_value
1571 .take()
1572 .expect("BUG: list frame started twice");
1573 let rows = self.start(raw_value)?;
1574 self.next_row(rows)
1575 }
1576 Some(mut rows) => {
1577 let Some(ResolveOutput::Level(row_data)) = input else {
1578 panic!("BUG: list frame resumed without a row result");
1579 };
1580 match self.finish_row(&mut rows, row_data)? {
1581 Some(veto) => Ok(ResolveStep::Return(ResolveOutput::Binding(
1582 veto,
1583 RowOverrides::default(),
1584 ))),
1585 None => self.next_row(rows),
1586 }
1587 }
1588 }
1589 }
1590
1591 fn start(&self, raw_value: EvaluateDataValue) -> Result<ListRows<'a>, Error> {
1592 let plan = self.plan;
1593 let source = plan
1594 .instance_lists
1595 .get(self.path)
1596 .expect("BUG: instance list data without a source");
1597 let template = plan.row_template(&source.identity);
1598 let Some(raw_rows) = raw_value.into_list() else {
1599 return Err(request_error(
1600 &self.name,
1601 format!(
1602 "rows of a `{}` list are an array of objects",
1603 source.identity.spec
1604 ),
1605 ));
1606 };
1607 if raw_rows.len() > self.limits.max_list_rows {
1608 return Err(request_error(
1609 &self.name,
1610 format!(
1611 "has {} rows, over max_list_rows {}",
1612 raw_rows.len(),
1613 self.limits.max_list_rows
1614 ),
1615 ));
1616 }
1617 if self.level == ListLevel::Top {
1618 check_list_tree(template, &raw_rows, self.limits)
1619 .map_err(|message| request_error(&self.name, message))?;
1620 }
1621 let row_type = Arc::new(LemmaType::primitive(TypeSpecification::Spec {
1622 spec: source.identity.clone(),
1623 }));
1624 let key_slots = match &source.key {
1625 ListKey::Fields(key_fields) => key_fields
1626 .iter()
1627 .map(|field| KeySlot {
1628 field,
1629 slot: template
1630 .data
1631 .get_index_of(&field.path)
1632 .expect("BUG: key field is template data"),
1633 field_type: row_end_type(template, &RowEnd::Data(field.path.clone())),
1634 })
1635 .collect(),
1636 ListKey::Numbered => Box::default(),
1637 };
1638 Ok(ListRows {
1639 source,
1640 identity: Arc::new(source.identity.clone()),
1641 template,
1642 row_type,
1643 literals: Vec::with_capacity(raw_rows.len()),
1644 key_slots,
1645 keyed: Vec::new(),
1646 number: 0,
1647 raw_rows: raw_rows.into_iter(),
1648 })
1649 }
1650
1651 fn next_row(&mut self, mut rows: ListRows<'a>) -> Result<ResolveStep<'a>, Error> {
1652 let Some(raw_row) = rows.raw_rows.next() else {
1653 return Ok(ResolveStep::Return(ResolveOutput::Binding(
1654 self.finish_list(rows),
1655 std::mem::take(&mut self.overrides),
1656 )));
1657 };
1658 rows.number += 1;
1659 let Some(fields) = raw_row.into_map() else {
1660 return Err(request_error(
1661 &self.name,
1662 format!("row {} must be an object", rows.number),
1663 ));
1664 };
1665 let template = rows.template;
1666 let origin = crate::planning::semantics::numbered_row_origin(&self.list_key, rows.number);
1667 let row_key: Arc<str> = match &self.under {
1668 Some(under) => Arc::from(format!("{under}.{origin}")),
1669 None => Arc::from(origin),
1670 };
1671 self.rows = Some(rows);
1672 Ok(ResolveStep::Call(ResolveFrame::Level(LevelFrame::new(
1673 template,
1674 Some(row_key),
1675 fields,
1676 self.limits,
1677 ListLevel::Nested,
1678 ))))
1679 }
1680
1681 fn finish_row(
1683 &mut self,
1684 rows: &mut ListRows<'a>,
1685 row_data: EvaluateData,
1686 ) -> Result<Option<OperationResult>, Error> {
1687 let number = rows.number;
1688 let name = self.name.as_str();
1689 let mut bindings: Vec<Option<RowBinding>> = vec![None; rows.template.data.len()];
1690 if let Some(veto) = row_data
1691 .row_overrides
1692 .fields
1693 .values()
1694 .flat_map(IndexMap::values)
1695 .find_map(|binding| match binding {
1696 OperationResult::Veto(veto) => Some(veto),
1697 OperationResult::Value(_) => None,
1698 })
1699 {
1700 return Ok(Some(OperationResult::Veto(VetoType::computation(format!(
1701 "In {name} row {number}: {veto}"
1702 )))));
1703 }
1704 for (slot, binding) in row_data.bindings {
1705 if let OperationResult::Veto(veto) = &binding {
1706 return Ok(Some(OperationResult::Veto(VetoType::computation(format!(
1707 "In {name} row {number}: {veto}"
1708 )))));
1709 }
1710 bindings[slot] = Some(RowBinding::Bound(binding));
1711 }
1712 if let ListKey::Fields(_) = &rows.source.key {
1713 let mut values = Vec::with_capacity(rows.key_slots.len());
1714 for KeySlot {
1715 field: key_field,
1716 slot,
1717 field_type,
1718 } in &rows.key_slots
1719 {
1720 let key_path = &key_field.path;
1721 let value = match bindings.get(*slot).and_then(Option::as_ref) {
1722 Some(RowBinding::Bound(OperationResult::Value(bound))) => {
1723 typed_from_bound(bound, field_type)
1724 }
1725 Some(RowBinding::Bound(OperationResult::Veto(_))) => {
1726 unreachable!("BUG: vetoed row binding returned above")
1727 }
1728 Some(
1729 RowBinding::Deferred { .. }
1730 | RowBinding::Rows { .. }
1731 | RowBinding::Instance(_),
1732 ) => {
1733 unreachable!("BUG: evaluation data rows bind resolved values")
1734 }
1735 None => default_of(&rows.template.data, key_path).ok_or_else(|| {
1736 request_error(
1737 name,
1738 format!(
1739 "row {number} has no value for key field '{}'",
1740 key_field.name
1741 ),
1742 )
1743 })?,
1744 };
1745 values.push(value);
1746 }
1747 rows.keyed.push(values);
1748 }
1749 let literal = row_literal(
1750 Arc::clone(&rows.identity),
1751 RowOrigin::Numbered {
1752 list: self.list_key.clone(),
1753 number,
1754 under: self.under.clone(),
1755 },
1756 bindings,
1757 Arc::clone(&rows.row_type),
1758 );
1759 let ValueKind::Spec(SpecValue::Row(key)) = &literal.value else {
1760 unreachable!("BUG: a row literal is a row value");
1761 };
1762 self.overrides.extend(row_data.row_overrides.under(key));
1763 rows.literals.push(literal);
1764 Ok(None)
1765 }
1766
1767 fn finish_list(&self, rows: ListRows<'a>) -> OperationResult {
1768 let literals: Arc<[TypedLiteral]> = rows.literals.into();
1769 let list = match &rows.source.key {
1770 ListKey::Fields(key_fields) => {
1771 let field_names: Vec<String> = key_fields
1772 .iter()
1773 .map(|key_field| key_field.name.clone())
1774 .collect();
1775 match KeyIndex::build_numbered(field_names, &rows.keyed) {
1776 Ok(index) => ListValue::indexed(literals, Arc::new(index)),
1777 Err(conflict) => {
1778 return OperationResult::Veto(VetoType::computation(format!(
1779 "{}: {conflict}",
1780 self.name
1781 )));
1782 }
1783 }
1784 }
1785 ListKey::Numbered => ListValue::from(literals),
1786 };
1787 OperationResult::from_literal(LiteralValue {
1788 value: ValueKind::List(list),
1789 })
1790 }
1791}
1792
1793#[cfg(test)]
1794mod tests {
1795 use super::*;
1796 use crate::computation::rational::{decimal_to_rational, rational_new, rational_one};
1797 use crate::planning::semantics::{
1798 primitive_number_arc, MeasureUnit, MeasureUnits, RatioUnit, RatioUnits, TypeExtends,
1799 };
1800 use rust_decimal::Decimal;
1801
1802 fn dummy_source() -> Source {
1803 Source::new(
1804 crate::parsing::source::SourceType::Volatile,
1805 crate::parsing::ast::Span {
1806 start: 0,
1807 end: 0,
1808 line: 1,
1809 col: 1,
1810 },
1811 )
1812 }
1813
1814 fn mass_measure_type() -> Arc<LemmaType> {
1815 Arc::new(LemmaType::new(
1816 "Mass".to_string(),
1817 TypeSpecification::Measure {
1818 minimum: None,
1819 maximum: None,
1820 units: MeasureUnits::from(vec![
1821 MeasureUnit {
1822 name: "kilogram".to_string(),
1823 scale: crate::literals::UnitScale::Literal(rational_one()),
1824 derived_measure_factors: Vec::new(),
1825 decomposition: crate::literals::BaseMeasureVector::new(),
1826 minimum: None,
1827 maximum: None,
1828 suggestion_magnitude: None,
1829 },
1830 MeasureUnit {
1831 name: "gram".to_string(),
1832 scale: crate::literals::UnitScale::Literal(
1833 decimal_to_rational(Decimal::new(1, 3)).expect("factor"),
1834 ),
1835 derived_measure_factors: Vec::new(),
1836 decomposition: crate::literals::BaseMeasureVector::new(),
1837 minimum: None,
1838 maximum: None,
1839 suggestion_magnitude: None,
1840 },
1841 ]),
1842 traits: Vec::new(),
1843 decomposition: None,
1844 unit: None,
1845 help: String::new(),
1846 },
1847 TypeExtends::Primitive,
1848 ))
1849 }
1850
1851 fn ratio_with_percent_type() -> Arc<LemmaType> {
1852 Arc::new(LemmaType::new(
1853 "Rate".to_string(),
1854 TypeSpecification::Ratio {
1855 minimum: None,
1856 maximum: None,
1857 units: RatioUnits::from(vec![
1858 RatioUnit {
1859 name: "percent".to_string(),
1860 value: decimal_to_rational(Decimal::new(100, 0)).expect("factor"),
1861 minimum: None,
1862 maximum: None,
1863 suggestion_magnitude: None,
1864 },
1865 RatioUnit {
1866 name: "fraction".to_string(),
1867 value: rational_one(),
1868 minimum: None,
1869 maximum: None,
1870 suggestion_magnitude: None,
1871 },
1872 ]),
1873 unit: None,
1874 help: String::new(),
1875 },
1876 TypeExtends::Primitive,
1877 ))
1878 }
1879
1880 #[test]
1881 fn string_input_parsed_against_type() {
1882 let ty = primitive_number_arc();
1883 let lit = parse_data_value(
1884 &EvaluateDataValue::Literal("42".to_string()),
1885 ty,
1886 &dummy_source(),
1887 &UnitIndex::new(),
1888 &IndexMap::new(),
1889 )
1890 .unwrap();
1891 assert!(matches!(lit.value, ValueKind::Number(_)));
1892 }
1893
1894 fn unit_map(unit: &str, magnitude: &str) -> EvaluateDataValue {
1895 let mut map = IndexMap::new();
1896 map.insert(
1897 unit.to_string(),
1898 EvaluateDataValue::Literal(magnitude.into()),
1899 );
1900 EvaluateDataValue::Map(map)
1901 }
1902
1903 #[test]
1904 fn single_unit_measure_map_parses() {
1905 let ty = mass_measure_type();
1906 let lit = parse_data_value(
1907 &unit_map("kilogram", "2"),
1908 &ty,
1909 &dummy_source(),
1910 &UnitIndex::new(),
1911 &IndexMap::new(),
1912 )
1913 .unwrap();
1914 let ValueKind::Measure(magnitude) = &lit.value else {
1915 panic!("expected measure");
1916 };
1917 assert_eq!(magnitude, &rational_new(2, 1));
1918 }
1919
1920 #[test]
1921 fn single_unit_ratio_map_parses() {
1922 let ty = ratio_with_percent_type();
1923 let lit = parse_data_value(
1924 &unit_map("percent", "5"),
1925 &ty,
1926 &dummy_source(),
1927 &UnitIndex::new(),
1928 &IndexMap::new(),
1929 )
1930 .unwrap();
1931 let ValueKind::Ratio(canonical) = &lit.value else {
1932 panic!("expected ratio");
1933 };
1934 assert_eq!(
1935 *canonical,
1936 decimal_to_rational(Decimal::new(5, 2)).expect("canonical")
1937 );
1938 }
1939
1940 #[test]
1941 fn multi_key_map_is_shape_error() {
1942 let mut map = IndexMap::new();
1943 map.insert(
1944 "kilogram".to_string(),
1945 EvaluateDataValue::Literal("2".to_string()),
1946 );
1947 map.insert(
1948 "gram".to_string(),
1949 EvaluateDataValue::Literal("2000".into()),
1950 );
1951 let message = evaluate_data_shape_error(&EvaluateDataValue::Map(map), &mass_measure_type())
1952 .expect("shape");
1953 assert!(message.contains("single-unit"), "{message}");
1954 }
1955
1956 #[test]
1957 fn empty_map_is_shape_error() {
1958 let message = evaluate_data_shape_error(
1959 &EvaluateDataValue::Map(IndexMap::new()),
1960 &mass_measure_type(),
1961 )
1962 .expect("shape");
1963 assert!(message.contains("single-unit"), "{message}");
1964 }
1965
1966 #[test]
1967 fn map_on_text_is_shape_error() {
1968 let message = evaluate_data_shape_error(
1969 &unit_map("eur", "84"),
1970 &LemmaType::primitive(TypeSpecification::Text {
1971 length: None,
1972 help: String::new(),
1973 }),
1974 )
1975 .expect("shape");
1976 assert!(message.contains("text"), "{message}");
1977 }
1978
1979 #[test]
1980 fn list_on_scalar_is_shape_error() {
1981 let message = evaluate_data_shape_error(
1982 &EvaluateDataValue::List(vec![EvaluateDataValue::Literal("1".to_string())]),
1983 &LemmaType::primitive(TypeSpecification::Number {
1984 minimum: None,
1985 maximum: None,
1986 decimals: None,
1987 help: String::new(),
1988 }),
1989 )
1990 .expect("shape");
1991 assert!(message.contains("array"), "{message}");
1992 }
1993
1994 #[test]
1995 fn boolean_on_number_is_shape_error() {
1996 let message = evaluate_data_shape_error(
1997 &EvaluateDataValue::Boolean(true),
1998 &LemmaType::primitive(TypeSpecification::Number {
1999 minimum: None,
2000 maximum: None,
2001 decimals: None,
2002 help: String::new(),
2003 }),
2004 )
2005 .expect("shape");
2006 assert!(message.contains("boolean"), "{message}");
2007 }
2008
2009 fn mass_list_type() -> LemmaType {
2010 LemmaType::list_of(mass_measure_type())
2011 }
2012
2013 #[test]
2014 fn nested_boolean_item_is_shape_error() {
2015 let message = evaluate_data_shape_error(
2016 &EvaluateDataValue::List(vec![
2017 EvaluateDataValue::Literal("1 kilogram".to_string()),
2018 EvaluateDataValue::Boolean(true),
2019 ]),
2020 &mass_list_type(),
2021 )
2022 .expect("shape");
2023 assert!(
2024 message.starts_with("list item 2: boolean input is only valid for boolean data"),
2025 "{message}"
2026 );
2027 }
2028
2029 #[test]
2030 fn nested_map_item_is_shape_error() {
2031 let text_list =
2032 LemmaType::list_of(Arc::new(LemmaType::primitive(TypeSpecification::Text {
2033 length: None,
2034 help: String::new(),
2035 })));
2036 let message = evaluate_data_shape_error(
2037 &EvaluateDataValue::List(vec![unit_map("eur", "84")]),
2038 &text_list,
2039 )
2040 .expect("shape");
2041 assert!(
2042 message.starts_with("list item 1: object overlay"),
2043 "{message}"
2044 );
2045 }
2046
2047 #[test]
2048 fn nested_list_item_is_shape_error() {
2049 let message = evaluate_data_shape_error(
2050 &EvaluateDataValue::List(vec![EvaluateDataValue::List(vec![
2051 EvaluateDataValue::Literal("1 eur".to_string()),
2052 ])]),
2053 &mass_list_type(),
2054 )
2055 .expect("shape");
2056 assert!(
2057 message.starts_with("list item 1: array overlay"),
2058 "{message}"
2059 );
2060 }
2061
2062 #[test]
2063 fn deeply_nested_item_reports_full_item_path() {
2064 let nested = LemmaType::list_of(Arc::new(mass_list_type()));
2065 let message = evaluate_data_shape_error(
2066 &EvaluateDataValue::List(vec![
2067 EvaluateDataValue::List(vec![EvaluateDataValue::Literal("1 kilogram".to_string())]),
2068 EvaluateDataValue::List(vec![EvaluateDataValue::Boolean(true)]),
2069 ]),
2070 &nested,
2071 )
2072 .expect("shape");
2073 assert!(
2074 message
2075 .starts_with("list item 2, item 1: boolean input is only valid for boolean data"),
2076 "{message}"
2077 );
2078 }
2079
2080 #[test]
2081 fn nested_literal_under_list_type_is_shape_error() {
2082 let nested = LemmaType::list_of(Arc::new(mass_list_type()));
2083 let message = evaluate_data_shape_error(
2084 &EvaluateDataValue::List(vec![EvaluateDataValue::Literal("1 kilogram".to_string())]),
2085 &nested,
2086 )
2087 .expect("shape");
2088 assert!(
2089 message.starts_with("list item 1: literal overlay"),
2090 "{message}"
2091 );
2092 }
2093
2094 #[test]
2095 fn valid_nested_measure_list_has_no_shape_error() {
2096 let message = evaluate_data_shape_error(
2097 &EvaluateDataValue::List(vec![
2098 unit_map("kilogram", "2"),
2099 EvaluateDataValue::Literal("500 gram".to_string()),
2100 ]),
2101 &mass_list_type(),
2102 );
2103 assert!(message.is_none(), "{message:?}");
2104 }
2105
2106 fn load_engine(code: &str) -> crate::Engine {
2107 let mut engine = crate::Engine::new();
2108 engine
2109 .load([(crate::SourceType::Volatile, code.to_string())])
2110 .expect("load");
2111 engine
2112 }
2113
2114 fn ordered(pairs: &[(&str, EvaluateDataValue)]) -> IndexMap<String, EvaluateDataValue> {
2115 pairs
2116 .iter()
2117 .map(|(k, v)| (k.to_string(), v.clone()))
2118 .collect()
2119 }
2120
2121 fn sample_shallow() -> EvaluateDataValue {
2122 EvaluateDataValue::Map(ordered(&[
2123 (
2124 "price",
2125 EvaluateDataValue::Map(ordered(&[("eur", EvaluateDataValue::from("84"))])),
2126 ),
2127 (
2128 "fares",
2129 EvaluateDataValue::List(vec![
2130 EvaluateDataValue::from("89 eur"),
2131 EvaluateDataValue::Boolean(true),
2132 EvaluateDataValue::Map(IndexMap::new()),
2133 EvaluateDataValue::List(Vec::new()),
2134 ]),
2135 ),
2136 ]))
2137 }
2138
2139 #[test]
2140 fn debug_matches_derived_layout_compact_and_pretty() {
2141 let value = sample_shallow();
2142 assert_eq!(
2143 format!("{value:?}"),
2144 r#"Map({"price": Map({"eur": Literal("84")}), "fares": List([Literal("89 eur"), Boolean(true), Map({}), List([])])})"#
2145 );
2146 assert_eq!(
2147 format!("{value:#?}"),
2148 concat!(
2149 "Map(\n",
2150 " {\n",
2151 " \"price\": Map(\n",
2152 " {\n",
2153 " \"eur\": Literal(\n",
2154 " \"84\",\n",
2155 " ),\n",
2156 " },\n",
2157 " ),\n",
2158 " \"fares\": List(\n",
2159 " [\n",
2160 " Literal(\n",
2161 " \"89 eur\",\n",
2162 " ),\n",
2163 " Boolean(\n",
2164 " true,\n",
2165 " ),\n",
2166 " Map(\n",
2167 " {},\n",
2168 " ),\n",
2169 " List(\n",
2170 " [],\n",
2171 " ),\n",
2172 " ],\n",
2173 " ),\n",
2174 " },\n",
2175 ")",
2176 )
2177 );
2178 }
2179
2180 #[test]
2181 fn deep_chains_clone_eq_debug_and_drop_on_small_stack() {
2182 const DEPTH: usize = 1_000_000;
2183 std::thread::Builder::new()
2184 .stack_size(256 * 1024)
2185 .spawn(|| {
2186 let mut list = EvaluateDataValue::Literal("leaf".to_string());
2187 for _ in 0..DEPTH {
2188 list = EvaluateDataValue::List(vec![list]);
2189 }
2190 let cloned = list.clone();
2191 assert!(list == cloned);
2192 let rendered = format!("{list:?}");
2193 assert_eq!(
2194 rendered.len(),
2195 DEPTH * "List([".len() + "Literal(\"leaf\")".len() + DEPTH * "])".len()
2196 );
2197 drop(cloned);
2198
2199 let mut map = EvaluateDataValue::Boolean(false);
2200 for _ in 0..DEPTH {
2201 let mut level = IndexMap::new();
2202 level.insert("n".to_string(), map);
2203 map = EvaluateDataValue::Map(level);
2204 }
2205 let cloned = map.clone();
2206 assert!(map == cloned);
2207 assert!(map != list);
2208 drop(cloned);
2209 drop(map);
2210 drop(list);
2211 })
2212 .expect("spawn small-stack thread")
2213 .join()
2214 .expect("deep run data must not overflow the stack");
2215 }
2216
2217 #[test]
2218 fn eq_is_order_insensitive_for_maps_and_ordered_for_lists() {
2219 let forward = EvaluateDataValue::Map(ordered(&[
2220 ("a", EvaluateDataValue::from("1")),
2221 ("b", EvaluateDataValue::from("2")),
2222 ]));
2223 let reversed = EvaluateDataValue::Map(ordered(&[
2224 ("b", EvaluateDataValue::from("2")),
2225 ("a", EvaluateDataValue::from("1")),
2226 ]));
2227 assert!(forward == reversed);
2228 assert!(
2229 EvaluateDataValue::List(vec![
2230 EvaluateDataValue::from("1"),
2231 EvaluateDataValue::from("2")
2232 ]) != EvaluateDataValue::List(vec![
2233 EvaluateDataValue::from("2"),
2234 EvaluateDataValue::from("1")
2235 ])
2236 );
2237 assert!(EvaluateDataValue::from("1") != EvaluateDataValue::Boolean(true));
2238 }
2239
2240 #[test]
2241 fn request_error_names_first_key_in_caller_order() {
2242 let engine = load_engine(
2243 r#"
2244spec inner
2245data n: 1
2246
2247spec s
2248uses left: inner
2249uses right: inner
2250rule out: 1
2251"#,
2252 );
2253 let now = crate::DateTimeValue::now();
2254 let overlay = EvaluateDataValue::Map(ordered(&[("n", EvaluateDataValue::from("2"))]));
2255 let left_first = engine
2256 .evaluate(
2257 None,
2258 "s",
2259 Some(&now),
2260 ordered(&[("left", overlay.clone()), ("right", overlay.clone())]),
2261 None,
2262 false,
2263 )
2264 .expect_err("uses alias map is a request error");
2265 assert!(left_first.to_string().contains("'left'"), "{left_first}");
2266 assert!(!left_first.to_string().contains("'right'"), "{left_first}");
2267
2268 let right_first = engine
2269 .evaluate(
2270 None,
2271 "s",
2272 Some(&now),
2273 ordered(&[("right", overlay.clone()), ("left", overlay)]),
2274 None,
2275 false,
2276 )
2277 .expect_err("uses alias map is a request error");
2278 assert!(right_first.to_string().contains("'right'"), "{right_first}");
2279 assert!(!right_first.to_string().contains("'left'"), "{right_first}");
2280 }
2281
2282 fn roster_engine() -> crate::Engine {
2283 load_engine(
2284 r#"
2285spec person
2286data age: number
2287 -> minimum 0
2288data score: number
2289 -> minimum 0
2290
2291spec roster
2292uses person
2293data people: person list
2294rule n: people -> count
2295"#,
2296 )
2297 }
2298
2299 #[test]
2300 fn row_veto_names_first_field_in_caller_order() {
2301 let engine = roster_engine();
2302 let now = crate::DateTimeValue::now();
2303 let age_first = engine
2304 .evaluate(
2305 None,
2306 "roster",
2307 Some(&now),
2308 ordered(&[(
2309 "people",
2310 EvaluateDataValue::List(vec![EvaluateDataValue::Map(ordered(&[
2311 ("age", EvaluateDataValue::from("-1")),
2312 ("score", EvaluateDataValue::from("-1")),
2313 ]))]),
2314 )]),
2315 Some(&["n".to_string()]),
2316 false,
2317 )
2318 .expect("evaluate");
2319 let reason = age_first
2320 .results
2321 .get("n")
2322 .expect("n")
2323 .veto_reason
2324 .as_deref()
2325 .expect("veto");
2326 assert!(reason.contains("age"), "{reason}");
2327 assert!(!reason.contains("score"), "{reason}");
2328
2329 let score_first = engine
2330 .evaluate(
2331 None,
2332 "roster",
2333 Some(&now),
2334 ordered(&[(
2335 "people",
2336 EvaluateDataValue::List(vec![EvaluateDataValue::Map(ordered(&[
2337 ("score", EvaluateDataValue::from("-1")),
2338 ("age", EvaluateDataValue::from("-1")),
2339 ]))]),
2340 )]),
2341 Some(&["n".to_string()]),
2342 false,
2343 )
2344 .expect("evaluate");
2345 let reason = score_first
2346 .results
2347 .get("n")
2348 .expect("n")
2349 .veto_reason
2350 .as_deref()
2351 .expect("veto");
2352 assert!(reason.contains("score"), "{reason}");
2353 assert!(!reason.contains("age"), "{reason}");
2354 }
2355
2356 #[test]
2357 fn row_unknown_fields_are_ignored() {
2358 let engine = roster_engine();
2359 let now = crate::DateTimeValue::now();
2360 let bogus_first = engine
2361 .evaluate(
2362 None,
2363 "roster",
2364 Some(&now),
2365 ordered(&[(
2366 "people",
2367 EvaluateDataValue::List(vec![EvaluateDataValue::Map(ordered(&[
2368 ("bogus", EvaluateDataValue::from("1")),
2369 ("also", EvaluateDataValue::from("2")),
2370 ("age", EvaluateDataValue::from("1")),
2371 ("score", EvaluateDataValue::from("1")),
2372 ]))]),
2373 )]),
2374 None,
2375 false,
2376 )
2377 .expect("unknown row fields are ignored");
2378 assert!(bogus_first.results.get("n").is_some());
2379
2380 let also_first = engine
2381 .evaluate(
2382 None,
2383 "roster",
2384 Some(&now),
2385 ordered(&[(
2386 "people",
2387 EvaluateDataValue::List(vec![EvaluateDataValue::Map(ordered(&[
2388 ("also", EvaluateDataValue::from("2")),
2389 ("bogus", EvaluateDataValue::from("1")),
2390 ("age", EvaluateDataValue::from("1")),
2391 ("score", EvaluateDataValue::from("1")),
2392 ]))]),
2393 )]),
2394 None,
2395 false,
2396 )
2397 .expect("unknown row fields are ignored");
2398 assert!(also_first.results.get("n").is_some());
2399 }
2400
2401 fn tags_engine(limits: crate::ResourceLimits) -> crate::Engine {
2402 let mut engine = crate::Engine::with_limits(limits);
2403 engine
2404 .load([(
2405 crate::SourceType::Volatile,
2406 "spec tagged\ndata tags: text list\nrule n: tags -> count".to_string(),
2407 )])
2408 .expect("load");
2409 engine
2410 }
2411
2412 fn evaluate_tags(engine: &crate::Engine, tags: Vec<String>) -> crate::Response {
2413 let now = crate::DateTimeValue::now();
2414 engine
2415 .evaluate(
2416 None,
2417 "tagged",
2418 Some(&now),
2419 ordered(&[(
2420 "tags",
2421 EvaluateDataValue::List(
2422 tags.into_iter().map(EvaluateDataValue::Literal).collect(),
2423 ),
2424 )]),
2425 Some(&["n".to_string()]),
2426 false,
2427 )
2428 .expect("evaluate")
2429 }
2430
2431 #[test]
2432 fn value_list_over_value_byte_limit_binds() {
2433 let engine = tags_engine(crate::ResourceLimits::default());
2434 let limit = crate::ResourceLimits::default().max_data_value_bytes;
2435 let tags: Vec<String> = (0..200).map(|i| format!("customer-tag-{i:04}")).collect();
2436 let total: usize = tags.iter().map(String::len).sum();
2437 assert!(
2438 total > limit,
2439 "fixture must exceed {limit} bytes, has {total}"
2440 );
2441
2442 let response = evaluate_tags(&engine, tags);
2443 let result = response.results.get("n").expect("n");
2444 assert_eq!(result.veto_reason, None);
2445 assert_eq!(result.result(), Some("200"));
2446 }
2447
2448 #[test]
2449 fn value_list_item_over_value_byte_limit_vetoes() {
2450 let engine = tags_engine(crate::ResourceLimits {
2451 max_data_value_bytes: 50,
2452 ..crate::ResourceLimits::default()
2453 });
2454 let response = evaluate_tags(
2455 &engine,
2456 vec!["vip".to_string(), "x".repeat(51), "trial".to_string()],
2457 );
2458 let reason = response
2459 .results
2460 .get("n")
2461 .expect("n")
2462 .veto_reason
2463 .as_deref()
2464 .expect("veto");
2465 assert!(
2466 reason.contains("list item 2 exceeds the size limit."),
2467 "{reason}"
2468 );
2469 }
2470}