1use crate::computation::{OperationResult, VetoType};
2use crate::parsing::ast::{ascii_lowercase_logical_name, canonical_logical_name};
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, row_literal, typed_literal_from_parsed,
9 typed_literal_with_binding_unit, BoundValueKind, DataDefinition, DataPath, InstanceKey,
10 KeyField, LemmaType, ListKey, ListValue, LiteralValue, RowBinding, RowEnd, RowOrigin, Source,
11 SpecIdentity, SpecValue, TypeSpecification, TypedLiteral, 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
572fn single_unit_literal_map(map: &IndexMap<String, EvaluateDataValue>) -> Option<(&str, &str)> {
578 if map.len() != 1 {
579 return None;
580 }
581 let (unit, value) = map.iter().next()?;
582 match value {
583 EvaluateDataValue::Literal(magnitude) => Some((magnitude.as_str(), unit.as_str())),
584 _ => None,
585 }
586}
587
588enum ShapeCheck<'a> {
589 Accepts,
590 Mismatch(String),
591 Rows {
592 rows: &'a [EvaluateDataValue],
593 element: &'a LemmaType,
594 },
595}
596
597fn shape_check<'a>(input: &'a EvaluateDataValue, lemma_type: &'a LemmaType) -> ShapeCheck<'a> {
598 let type_spec = &lemma_type.specifications;
599 match (input, type_spec) {
600 (EvaluateDataValue::Literal(_), TypeSpecification::List { .. }) => ShapeCheck::Mismatch(
601 format!("literal overlay is not valid for list data ({type_spec})"),
602 ),
603 (EvaluateDataValue::Literal(_), _) => ShapeCheck::Accepts,
604 (EvaluateDataValue::Boolean(_), TypeSpecification::Boolean { .. }) => ShapeCheck::Accepts,
605 (EvaluateDataValue::Boolean(_), _) => ShapeCheck::Mismatch(format!(
606 "boolean input is only valid for boolean data, not {type_spec}"
607 )),
608 (
609 EvaluateDataValue::Map(map),
610 TypeSpecification::Measure { .. } | TypeSpecification::Ratio { .. },
611 ) => {
612 if single_unit_literal_map(map).is_some() {
613 ShapeCheck::Accepts
614 } else {
615 ShapeCheck::Mismatch(
616 "measure and ratio data must be a convenience string or a single-unit map"
617 .to_string(),
618 )
619 }
620 }
621 (EvaluateDataValue::Map(_), _) => ShapeCheck::Mismatch(format!(
622 "object overlay is only valid for measure or ratio data, not {type_spec}"
623 )),
624 (EvaluateDataValue::List(rows), TypeSpecification::List { .. }) => ShapeCheck::Rows {
625 rows: rows.as_slice(),
626 element: lemma_type.list_element_lemma_type().as_ref(),
627 },
628 (EvaluateDataValue::List(_), _) => ShapeCheck::Mismatch(format!(
629 "array overlay is not valid for scalar data ({type_spec})"
630 )),
631 }
632}
633
634struct ShapeFrame<'a> {
635 rows: std::slice::Iter<'a, EvaluateDataValue>,
636 element: &'a LemmaType,
637 position: usize,
638}
639
640fn evaluate_data_shape_error(input: &EvaluateDataValue, lemma_type: &LemmaType) -> Option<String> {
643 let mut frames: Vec<ShapeFrame<'_>> = Vec::new();
644 let mut pending = Some((input, lemma_type));
645 loop {
646 if let Some((value, ty)) = pending.take() {
647 match shape_check(value, ty) {
648 ShapeCheck::Accepts => {}
649 ShapeCheck::Mismatch(message) => return Some(item_path_message(&frames, message)),
650 ShapeCheck::Rows { rows, element } => frames.push(ShapeFrame {
651 rows: rows.iter(),
652 element,
653 position: 0,
654 }),
655 }
656 }
657 let frame = frames.last_mut()?;
658 match frame.rows.next() {
659 Some(child) => {
660 frame.position += 1;
661 pending = Some((child, frame.element));
662 }
663 None => {
664 frames.pop();
665 }
666 }
667 }
668}
669
670fn item_path_message(frames: &[ShapeFrame<'_>], message: String) -> String {
671 if frames.is_empty() {
672 return message;
673 }
674 let positions: Vec<String> = frames.iter().map(|f| f.position.to_string()).collect();
675 format!("list item {}: {message}", positions.join(", item "))
676}
677
678#[derive(Debug, Clone, Default)]
681pub(crate) struct RowOverrides {
682 pub(crate) fields: IndexMap<InstanceKey, IndexMap<usize, OperationResult>>,
683 pub(crate) keyed_lists: HashSet<(Option<InstanceKey>, DataPath)>,
686}
687
688impl RowOverrides {
689 fn extend(&mut self, other: RowOverrides) {
690 for (key, fields) in other.fields {
691 self.fields.entry(key).or_default().extend(fields);
692 }
693 self.keyed_lists.extend(other.keyed_lists);
694 }
695
696 fn under(self, owner: &InstanceKey) -> RowOverrides {
703 RowOverrides {
704 fields: self
705 .fields
706 .into_iter()
707 .map(|(key, fields)| {
708 let key = if instance_key_has_numbered_origin(&key) {
709 key
710 } else {
711 key.under(owner)
712 };
713 (key, fields)
714 })
715 .collect(),
716 keyed_lists: self
717 .keyed_lists
718 .into_iter()
719 .map(|(list_owner, path)| {
720 let list_owner = match list_owner {
721 Some(key) if instance_key_has_numbered_origin(&key) => key,
722 Some(key) => key.under(owner),
723 None => owner.clone(),
724 };
725 (Some(list_owner), path)
726 })
727 .collect(),
728 }
729 }
730}
731
732fn instance_key_has_numbered_origin(key: &InstanceKey) -> bool {
733 let mut current = Some(key);
734 while let Some(instance) = current {
735 if matches!(instance.row.origin, RowOrigin::Numbered { .. }) {
736 return true;
737 }
738 current = instance.owner();
739 }
740 false
741}
742
743#[derive(Debug, Clone, Default)]
749pub struct EvaluateData {
750 pub bindings: IndexMap<usize, OperationResult>,
753 pub(crate) row_overrides: RowOverrides,
756 pub ignored_unknown: Vec<String>,
758}
759
760impl EvaluateData {
761 pub fn resolve(
767 plan: &ExecutionPlan,
768 raw_values: IndexMap<String, EvaluateDataValue>,
769 limits: &ResourceLimits,
770 ) -> Result<Self, Error> {
771 Self::resolve_level(plan, raw_values, limits, ListLevel::Top)
772 }
773
774 fn resolve_level(
775 plan: &ExecutionPlan,
776 raw_values: IndexMap<String, EvaluateDataValue>,
777 limits: &ResourceLimits,
778 level: ListLevel,
779 ) -> Result<Self, Error> {
780 if level == ListLevel::Top {
781 reject_whole_list_with_row_keys(plan, &raw_values)?;
782 }
783 match run_resolve(ResolveFrame::Level(LevelFrame::new(
784 plan, None, raw_values, limits, level,
785 )))? {
786 ResolveOutput::Level(evaluate_data) => Ok(evaluate_data),
787 ResolveOutput::Binding(..) => {
788 panic!("BUG: root level frame returned a list binding")
789 }
790 }
791 }
792}
793
794enum ResolveOutput {
795 Level(EvaluateData),
796 Binding(OperationResult, RowOverrides),
798}
799
800enum ResolveStep<'a> {
801 Call(ResolveFrame<'a>),
802 Return(ResolveOutput),
803}
804
805enum ResolveFrame<'a> {
806 Level(LevelFrame<'a>),
807 List(ListFrame<'a>),
808}
809
810impl<'a> ResolveFrame<'a> {
811 fn resume(&mut self, input: Option<ResolveOutput>) -> Result<ResolveStep<'a>, Error> {
812 match self {
813 ResolveFrame::Level(frame) => frame.resume(input),
814 ResolveFrame::List(frame) => frame.resume(input),
815 }
816 }
817}
818
819fn run_resolve(root: ResolveFrame<'_>) -> Result<ResolveOutput, Error> {
820 let mut frames = vec![root];
821 let mut input = None;
822 loop {
823 let frame = frames.last_mut().expect("BUG: resolve frame stack empty");
824 match frame.resume(input.take())? {
825 ResolveStep::Call(child) => frames.push(child),
826 ResolveStep::Return(output) => {
827 frames.pop();
828 if frames.is_empty() {
829 return Ok(output);
830 }
831 input = Some(output);
832 }
833 }
834 }
835}
836
837struct LevelFrame<'a> {
840 plan: &'a ExecutionPlan,
841 row_key: Option<Arc<str>>,
844 limits: &'a ResourceLimits,
845 level: ListLevel,
846 entries: indexmap::map::IntoIter<String, EvaluateDataValue>,
847 seen_canonical: HashSet<String>,
848 evaluate_data: EvaluateData,
849 awaiting: Option<AwaitBinding>,
850}
851
852enum AwaitBinding {
855 Data { slot: usize },
856 RowField { key: InstanceKey, slot: usize },
857}
858
859impl<'a> LevelFrame<'a> {
860 fn new(
861 plan: &'a ExecutionPlan,
862 row_key: Option<Arc<str>>,
863 raw_values: IndexMap<String, EvaluateDataValue>,
864 limits: &'a ResourceLimits,
865 level: ListLevel,
866 ) -> Self {
867 let capacity = raw_values.len();
868 Self {
869 plan,
870 row_key,
871 limits,
872 level,
873 entries: raw_values.into_iter(),
874 seen_canonical: HashSet::with_capacity(capacity),
875 evaluate_data: EvaluateData {
876 bindings: IndexMap::with_capacity(capacity),
877 ..EvaluateData::default()
878 },
879 awaiting: None,
880 }
881 }
882
883 fn resume(&mut self, input: Option<ResolveOutput>) -> Result<ResolveStep<'a>, Error> {
884 match (self.awaiting.take(), input) {
885 (None, None) => {}
886 (
887 Some(AwaitBinding::Data { slot }),
888 Some(ResolveOutput::Binding(binding, overrides)),
889 ) => {
890 self.evaluate_data.bindings.insert(slot, binding);
891 self.evaluate_data.row_overrides.extend(overrides);
892 }
893 (
894 Some(AwaitBinding::RowField { key, slot }),
895 Some(ResolveOutput::Binding(binding, overrides)),
896 ) => {
897 self.evaluate_data
898 .row_overrides
899 .fields
900 .entry(key)
901 .or_default()
902 .insert(slot, binding);
903 self.evaluate_data.row_overrides.extend(overrides);
904 }
905 _ => panic!("BUG: level frame resumed with an unexpected output"),
906 }
907 self.advance()
908 }
909
910 fn advance(&mut self) -> Result<ResolveStep<'a>, Error> {
911 let plan = self.plan;
912 let limits = self.limits;
913 let level = self.level;
914 let evaluate_data = &mut self.evaluate_data;
915 let seen_canonical = &mut self.seen_canonical;
916
917 for (name, raw_value) in self.entries.by_ref() {
918 let canonical = canonical_logical_name(&name);
919 if seen_canonical.contains(canonical.as_ref()) {
920 let canonical = canonical.into_owned();
921 return Err(Error::request(
922 format!("Duplicate data key '{canonical}'"),
923 Some("Data keys are case-insensitive; remove the duplicate"),
924 )
925 .with_related_data(name));
926 }
927 seen_canonical.insert(canonical.to_string());
928
929 let Some((slot, data_path, data_definition)) =
930 resolve_evaluate_data_target(plan, &canonical)
931 else {
932 let row_field = match resolve_row_field_key(plan, &canonical) {
933 RowKeyLookup::Field(row_field) => row_field,
934 RowKeyLookup::Unknown => {
935 evaluate_data.ignored_unknown.push(name);
936 continue;
937 }
938 RowKeyLookup::Invalid(error) => return Err(error.with_related_data(name)),
939 };
940 if matches!(row_field.definition, DataDefinition::Import { .. }) {
941 if matches!(
942 raw_value,
943 EvaluateDataValue::Map(_) | EvaluateDataValue::List(_)
944 ) {
945 return Err(Error::request(
946 format!(
947 "data '{name}': '{name}' is a uses alias; objects and arrays are not a nesting level"
948 ),
949 Some(format!("bind fields with dotted keys, e.g. '{name}.<field>'")),
950 )
951 .with_related_data(name));
952 }
953 evaluate_data.ignored_unknown.push(name);
954 continue;
955 }
956 if row_field.sealed {
957 return Err(Error::request(
958 format!(
959 "data '{name}' is sealed by `uses -> with` and cannot be supplied at evaluate"
960 ),
961 Some("remove this key, or change the `uses` binding if the value should be caller-owned data"),
962 )
963 .with_related_data(name));
964 }
965 if let Some(list) = row_field.keyed_list {
966 evaluate_data.row_overrides.keyed_lists.insert(list);
967 }
968 if matches!(row_field.definition, DataDefinition::InstanceList { .. }) {
969 self.awaiting = Some(AwaitBinding::RowField {
970 key: row_field.key,
971 slot: row_field.slot,
972 });
973 return Ok(ResolveStep::Call(ResolveFrame::List(ListFrame {
974 plan: row_field.template,
975 path: row_field.field,
976 list_key: canonical.into_owned(),
977 under: self.row_key.clone(),
978 name,
979 overrides: RowOverrides::default(),
980 limits,
981 level,
982 raw_value: Some(raw_value),
983 rows: None,
984 })));
985 }
986 let type_arc = Arc::clone(
987 row_field
988 .definition
989 .resolved_type_arc()
990 .expect("BUG: scalar row field is typed template data"),
991 );
992 let binding = resolve_scalar_binding(
993 plan,
994 &name,
995 &canonical,
996 &raw_value,
997 &type_arc,
998 row_field.definition.source(),
999 limits,
1000 )?;
1001 evaluate_data
1002 .row_overrides
1003 .fields
1004 .entry(row_field.key)
1005 .or_default()
1006 .insert(row_field.slot, binding);
1007 continue;
1008 };
1009
1010 if data_definition.is_bound() {
1011 return Err(Error::request(
1012 format!(
1013 "data '{name}' is sealed by `uses -> with` and cannot be supplied at evaluate"
1014 ),
1015 Some("remove this key, or change the `uses` binding if the value should be caller-owned data"),
1016 )
1017 .with_related_data(name));
1018 }
1019
1020 let data_source = data_definition.source();
1021 let type_arc = match data_definition {
1022 DataDefinition::TypeDeclaration { resolved_type, .. }
1023 | DataDefinition::Value { resolved_type, .. }
1024 | DataDefinition::Reference { resolved_type, .. } => Arc::clone(resolved_type),
1025 DataDefinition::Import { .. } => {
1026 if matches!(
1027 raw_value,
1028 EvaluateDataValue::Map(_) | EvaluateDataValue::List(_)
1029 ) {
1030 return Err(Error::request(
1031 format!(
1032 "data '{name}': '{name}' is a uses alias; objects and arrays are not a nesting level"
1033 ),
1034 Some(format!("bind fields with dotted keys, e.g. '{name}.<field>'")),
1035 )
1036 .with_related_data(name));
1037 }
1038 evaluate_data.ignored_unknown.push(name);
1039 continue;
1040 }
1041 DataDefinition::InstanceList { .. } => {
1042 self.awaiting = Some(AwaitBinding::Data { slot });
1043 return Ok(ResolveStep::Call(ResolveFrame::List(ListFrame {
1044 plan,
1045 path: data_path,
1046 list_key: data_path.input_key(),
1047 under: self.row_key.clone(),
1048 name,
1049 overrides: RowOverrides::default(),
1050 limits,
1051 level,
1052 raw_value: Some(raw_value),
1053 rows: None,
1054 })));
1055 }
1056 };
1057
1058 let binding = resolve_scalar_binding(
1059 plan,
1060 &name,
1061 &canonical,
1062 &raw_value,
1063 &type_arc,
1064 data_source,
1065 limits,
1066 )?;
1067 evaluate_data.bindings.insert(slot, binding);
1068 }
1069
1070 Ok(ResolveStep::Return(ResolveOutput::Level(std::mem::take(
1071 evaluate_data,
1072 ))))
1073 }
1074}
1075
1076fn resolve_scalar_binding(
1080 plan: &ExecutionPlan,
1081 name: &str,
1082 input_key: &str,
1083 raw_value: &EvaluateDataValue,
1084 type_arc: &Arc<LemmaType>,
1085 data_source: &Source,
1086 limits: &ResourceLimits,
1087) -> Result<OperationResult, Error> {
1088 if raw_value.is_empty() && type_arc.empty_runtime_input_vetoes() {
1089 return Ok(OperationResult::Veto(VetoType::computation(format!(
1090 "Data {} cannot be empty.",
1091 type_arc.label_for_data_input(input_key)
1092 ))));
1093 }
1094
1095 if let Some(message) = evaluate_data_shape_error(raw_value, type_arc) {
1096 return Err(Error::request(
1097 format!("data '{name}': {message}"),
1098 Some(format!("fix the overlay for '{name}'")),
1099 )
1100 .with_related_data(name));
1101 }
1102
1103 let typed = match parse_data_value(
1104 raw_value,
1105 type_arc,
1106 data_source,
1107 plan.expression_unit_index(),
1108 &plan.resolved_types.resolved,
1109 ) {
1110 Ok(value) => value,
1111 Err(error) => {
1112 return Ok(OperationResult::Veto(VetoType::computation(
1113 type_arc.data_veto_message(input_key, error.message()),
1114 )));
1115 }
1116 };
1117
1118 if let ValueKind::List(rows) = &typed.value {
1119 if rows.len() > limits.max_list_rows {
1120 return Err(Error::request(
1121 format!(
1122 "data '{name}' has {} rows, over max_list_rows {}",
1123 rows.len(),
1124 limits.max_list_rows
1125 ),
1126 Some("reduce the list size or raise max_list_rows".to_string()),
1127 )
1128 .with_related_data(name));
1129 }
1130 }
1131
1132 let literal_value = LiteralValue {
1133 value: typed.value.clone(),
1134 };
1135 let oversize = match &typed.value {
1136 ValueKind::List(items) => items
1137 .iter()
1138 .position(|item| item.byte_size() > limits.max_data_value_bytes)
1139 .map(|index| {
1140 format!(
1141 "Data {} list item {} exceeds the size limit.",
1142 type_arc.label_for_data_input(input_key),
1143 index + 1
1144 )
1145 }),
1146 _ => (literal_value.byte_size() > limits.max_data_value_bytes).then(|| {
1147 format!(
1148 "Data {} exceeds the size limit.",
1149 type_arc.label_for_data_input(input_key)
1150 )
1151 }),
1152 };
1153 if let Some(reason) = oversize {
1154 return Ok(OperationResult::Veto(VetoType::computation(reason)));
1155 }
1156
1157 if let Err(message) = validate_value_against_type(
1158 typed.lemma_type.as_ref(),
1159 &literal_value,
1160 plan.expression_unit_index(),
1161 None,
1162 ) {
1163 return Ok(OperationResult::Veto(VetoType::computation(
1164 type_arc.data_veto_message(input_key, &message),
1165 )));
1166 }
1167
1168 Ok(OperationResult::from_bound(BoundValueKind {
1169 value: typed.value.clone(),
1170 unit: typed.lemma_type.unit().map(std::sync::Arc::from),
1171 instance_factors: None,
1172 }))
1173}
1174
1175fn resolve_evaluate_data_target<'a>(
1179 plan: &'a ExecutionPlan,
1180 key: &str,
1181) -> Option<(usize, &'a DataPath, &'a DataDefinition)> {
1182 let path = plan.input_key_index.get(key)?;
1183 let (slot, path, definition) = plan
1184 .data
1185 .get_full(path)
1186 .unwrap_or_else(|| panic!("BUG: indexed input '{key}' has no data definition"));
1187 Some((slot, path, definition))
1188}
1189
1190type ListAddress = (Option<InstanceKey>, DataPath);
1192
1193struct RowFieldKey<'a> {
1196 key: InstanceKey,
1198 template: &'a ExecutionPlan,
1199 slot: usize,
1200 field: &'a DataPath,
1201 definition: &'a DataDefinition,
1202 sealed: bool,
1204 keyed_list: Option<ListAddress>,
1207 lists: Vec<(ListAddress, String)>,
1209}
1210
1211enum RowKeyLookup<'a> {
1212 Field(RowFieldKey<'a>),
1213 Unknown,
1215 Invalid(Error),
1218}
1219
1220fn resolve_row_field_key<'a>(plan: &'a ExecutionPlan, key: &str) -> RowKeyLookup<'a> {
1223 let parts: Vec<&str> = key.split('.').collect();
1224 let mut template = plan;
1225 let mut holder: Option<InstanceKey> = None;
1226 let mut keyed: Option<(ListAddress, &'a ListKey)> = None;
1227 let mut lists: Vec<(ListAddress, String)> = Vec::new();
1228 let mut index = 0;
1229 loop {
1230 let Some(path) = template.input_key_index.get(parts[index]) else {
1231 return unknown_under_list(key, &lists);
1232 };
1233 let (slot, _, definition) = template
1234 .data
1235 .get_full(path)
1236 .expect("BUG: indexed input key has a data definition");
1237 let row = holder.as_ref().map(|key| key.row.as_ref());
1238 if index + 1 == parts.len() {
1239 if matches!(definition, DataDefinition::Import { .. }) && !lists.is_empty() {
1240 return unknown_under_list(key, &lists);
1241 }
1242 let Some(key) = holder else {
1243 return RowKeyLookup::Unknown;
1244 };
1245 let keyed_list = keyed.and_then(|(list, list_key)| match list_key {
1246 ListKey::Fields(fields) => fields
1247 .iter()
1248 .any(|field| field.path == *path)
1249 .then_some(list),
1250 ListKey::Numbered => None,
1251 });
1252 return RowKeyLookup::Field(RowFieldKey {
1253 sealed: key.row.bindings[slot].is_some(),
1254 key,
1255 template,
1256 slot,
1257 field: path,
1258 definition,
1259 keyed_list,
1260 lists,
1261 });
1262 }
1263 match definition {
1264 DataDefinition::Import { .. } => {
1265 let import = Arc::clone(template.import_row(row, path, None));
1266 template = template.row_template(&import.identity);
1267 holder = Some(InstanceKey::new(import, holder));
1268 keyed = None;
1269 index += 1;
1270 }
1271 DataDefinition::InstanceList { .. } => {
1272 let list_key = parts[..=index].join(".");
1273 let source = template
1274 .instance_lists
1275 .get(path)
1276 .unwrap_or_else(|| panic!("BUG: instance list '{list_key}' has no source"));
1277 let (rows, owner) = match row.and_then(|row| row.bindings[slot].as_ref()) {
1278 None => (source.store.rows.as_slice(), holder.clone()),
1279 Some(RowBinding::Rows { rows, hops, .. }) => {
1280 let holder = holder.as_ref().expect("BUG: a bound list is a row field");
1281 (rows.as_slice(), holder.scope(*hops).cloned())
1282 }
1283 Some(
1284 RowBinding::Bound(_)
1285 | RowBinding::Deferred { .. }
1286 | RowBinding::Instance(_),
1287 ) => {
1288 return RowKeyLookup::Invalid(Error::request(
1289 format!(
1290 "data '{key}' is sealed by `uses -> with` and cannot be supplied at evaluate"
1291 ),
1292 Some(format!(
1293 "'{list_key}' is bound by `uses -> with`; its rows take no run data"
1294 )),
1295 ));
1296 }
1297 };
1298 let rest = &parts[index + 1..];
1299 let number = match rest.first().map(|row| row.parse::<usize>()) {
1300 Some(Ok(number)) if rest.len() > 1 => number,
1301 _ => {
1302 return RowKeyLookup::Invalid(no_list_data(
1303 key,
1304 &list_key,
1305 format!("fields of a row of '{list_key}' are keyed '{list_key}.<row>.<field>'"),
1306 ));
1307 }
1308 };
1309 if number == 0 || number > rows.len() {
1310 return RowKeyLookup::Invalid(no_list_data(
1311 key,
1312 &list_key,
1313 format!(
1314 "'{list_key}' has {} spec-written row(s); rows are numbered from 1",
1315 rows.len()
1316 ),
1317 ));
1318 }
1319 let list = (owner.clone(), path.clone());
1320 lists.push((list.clone(), list_key));
1321 keyed = match row.and_then(|row| row.bindings[slot].as_ref()) {
1322 None => Some((list, &source.key)),
1323 Some(_) => None,
1324 };
1325 let listed = Arc::clone(&rows[number - 1]);
1326 template = template.row_template(&listed.identity);
1327 holder = Some(InstanceKey::new(listed, owner));
1328 index += 2;
1329 }
1330 DataDefinition::Value { .. }
1331 | DataDefinition::TypeDeclaration { .. }
1332 | DataDefinition::Reference { .. } => {
1333 return unknown_under_list(key, &lists);
1334 }
1335 }
1336 }
1337}
1338
1339pub(crate) fn row_field_definition<'a>(
1342 plan: &'a ExecutionPlan,
1343 key: &str,
1344) -> Option<(
1345 &'a ExecutionPlan,
1346 &'a DataDefinition,
1347 InstanceKey,
1348 &'a DataPath,
1349)> {
1350 match resolve_row_field_key(plan, key) {
1351 RowKeyLookup::Field(row_field) if row_field.lists.is_empty() => Some((
1352 row_field.template,
1353 row_field.definition,
1354 row_field.key,
1355 row_field.field,
1356 )),
1357 RowKeyLookup::Field(_) => None,
1358 RowKeyLookup::Unknown | RowKeyLookup::Invalid(_) => None,
1359 }
1360}
1361
1362fn unknown_under_list<'a>(key: &str, lists: &[(ListAddress, String)]) -> RowKeyLookup<'a> {
1365 match lists.last() {
1366 Some((_, list_key)) => {
1367 let field = key[list_key.len() + 1..]
1368 .split_once('.')
1369 .map_or("", |(_, field)| field);
1370 RowKeyLookup::Invalid(no_list_data(
1371 key,
1372 list_key,
1373 format!("rows of '{list_key}' have no field '{field}'"),
1374 ))
1375 }
1376 None => RowKeyLookup::Unknown,
1377 }
1378}
1379
1380fn no_list_data(key: &str, list_key: &str, hint: String) -> Error {
1381 Error::request(
1382 format!("data '{key}' names no data of '{list_key}'"),
1383 Some(hint),
1384 )
1385}
1386
1387fn reject_whole_list_with_row_keys(
1390 plan: &ExecutionPlan,
1391 raw_values: &IndexMap<String, EvaluateDataValue>,
1392) -> Result<(), Error> {
1393 let mut whole_lists: HashSet<ListAddress> = HashSet::new();
1394 let mut row_fields: Vec<(&String, Vec<(ListAddress, String)>)> = Vec::new();
1395 for name in raw_values.keys() {
1396 let canonical = canonical_logical_name(name);
1397 if let Some((_, path, definition)) = resolve_evaluate_data_target(plan, &canonical) {
1398 if matches!(definition, DataDefinition::InstanceList { .. }) {
1399 whole_lists.insert((None, path.clone()));
1400 }
1401 continue;
1402 }
1403 if let RowKeyLookup::Field(row_field) = resolve_row_field_key(plan, &canonical) {
1404 if matches!(row_field.definition, DataDefinition::InstanceList { .. }) {
1405 whole_lists.insert((Some(row_field.key.clone()), row_field.field.clone()));
1406 }
1407 row_fields.push((name, row_field.lists));
1408 }
1409 }
1410 for (name, lists) in row_fields {
1411 for (list, list_key) in lists {
1412 if whole_lists.contains(&list) {
1413 return Err(Error::request(
1414 format!(
1415 "data '{list_key}': cannot supply both the whole list and dotted row keys such as '{name}'"
1416 ),
1417 Some(format!(
1418 "pass either '{list_key}' as an array, or only dotted keys under '{list_key}'"
1419 )),
1420 )
1421 .with_related_data(list_key));
1422 }
1423 }
1424 }
1425 Ok(())
1426}
1427
1428#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1431enum ListLevel {
1432 Top,
1433 Nested,
1434}
1435
1436fn check_list_tree(
1441 template: &ExecutionPlan,
1442 raw_rows: &[EvaluateDataValue],
1443 limits: &ResourceLimits,
1444) -> Result<(), String> {
1445 let mut stack: Vec<(&ExecutionPlan, &EvaluateDataValue, usize)> =
1446 raw_rows.iter().map(|row| (template, row, 2)).collect();
1447 let mut nodes = 0usize;
1448 let mut deepest = 0usize;
1449 while let Some((row_template, raw_row, depth)) = stack.pop() {
1450 nodes += 1;
1451 if nodes > limits.max_list_nodes {
1452 return Err(format!(
1453 "has more than max_list_nodes {} rows across all levels",
1454 limits.max_list_nodes
1455 ));
1456 }
1457 deepest = deepest.max(depth);
1458 let EvaluateDataValue::Map(fields) = raw_row else {
1459 continue;
1460 };
1461 for (field, value) in fields {
1462 let EvaluateDataValue::List(rows) = value else {
1463 continue;
1464 };
1465 let Some(source) = row_template
1466 .input_key_index
1467 .get(canonical_logical_name(field).as_ref())
1468 .and_then(|path| row_template.instance_lists.get(path))
1469 else {
1470 continue;
1471 };
1472 let nested = row_template.row_template(&source.identity);
1473 stack.extend(rows.iter().map(|row| (nested, row, depth + 1)));
1474 }
1475 }
1476 if deepest > limits.max_list_depth {
1477 return Err(format!(
1478 "rows nest {deepest} levels deep counting the spec itself, over max_list_depth {}",
1479 limits.max_list_depth
1480 ));
1481 }
1482 Ok(())
1483}
1484
1485struct ListFrame<'a> {
1489 plan: &'a ExecutionPlan,
1490 path: &'a DataPath,
1491 list_key: String,
1493 under: Option<Arc<str>>,
1496 name: String,
1497 overrides: RowOverrides,
1499 limits: &'a ResourceLimits,
1500 level: ListLevel,
1501 raw_value: Option<EvaluateDataValue>,
1502 rows: Option<ListRows<'a>>,
1503}
1504
1505struct ListRows<'a> {
1506 source: &'a InstanceListSource,
1507 identity: Arc<SpecIdentity>,
1509 template: &'a ExecutionPlan,
1510 row_type: Arc<LemmaType>,
1511 raw_rows: std::vec::IntoIter<EvaluateDataValue>,
1512 number: usize,
1513 literals: Vec<TypedLiteral>,
1514 key_slots: Box<[KeySlot<'a>]>,
1516 keyed: Vec<Vec<TypedLiteral>>,
1517}
1518
1519struct KeySlot<'a> {
1520 field: &'a KeyField,
1521 slot: usize,
1522 field_type: Arc<LemmaType>,
1523}
1524
1525fn request_error(name: &str, message: String) -> Error {
1526 Error::request(format!("data '{name}': {message}"), None::<String>).with_related_data(name)
1527}
1528
1529impl<'a> ListFrame<'a> {
1530 fn resume(&mut self, input: Option<ResolveOutput>) -> Result<ResolveStep<'a>, Error> {
1531 match self.rows.take() {
1532 None => {
1533 assert!(
1534 input.is_none(),
1535 "BUG: list frame started with a child output"
1536 );
1537 let raw_value = self
1538 .raw_value
1539 .take()
1540 .expect("BUG: list frame started twice");
1541 let rows = self.start(raw_value)?;
1542 self.next_row(rows)
1543 }
1544 Some(mut rows) => {
1545 let Some(ResolveOutput::Level(row_data)) = input else {
1546 panic!("BUG: list frame resumed without a row result");
1547 };
1548 match self.finish_row(&mut rows, row_data)? {
1549 Some(veto) => Ok(ResolveStep::Return(ResolveOutput::Binding(
1550 veto,
1551 RowOverrides::default(),
1552 ))),
1553 None => self.next_row(rows),
1554 }
1555 }
1556 }
1557 }
1558
1559 fn start(&self, raw_value: EvaluateDataValue) -> Result<ListRows<'a>, Error> {
1560 let plan = self.plan;
1561 let source = plan
1562 .instance_lists
1563 .get(self.path)
1564 .expect("BUG: instance list data without a source");
1565 let template = plan.row_template(&source.identity);
1566 let Some(raw_rows) = raw_value.into_list() else {
1567 return Err(request_error(
1568 &self.name,
1569 format!(
1570 "rows of a `{}` list are an array of objects",
1571 source.identity.spec
1572 ),
1573 ));
1574 };
1575 if raw_rows.len() > self.limits.max_list_rows {
1576 return Err(request_error(
1577 &self.name,
1578 format!(
1579 "has {} rows, over max_list_rows {}",
1580 raw_rows.len(),
1581 self.limits.max_list_rows
1582 ),
1583 ));
1584 }
1585 if self.level == ListLevel::Top {
1586 check_list_tree(template, &raw_rows, self.limits)
1587 .map_err(|message| request_error(&self.name, message))?;
1588 }
1589 let row_type = Arc::new(LemmaType::primitive(TypeSpecification::Spec {
1590 spec: source.identity.clone(),
1591 }));
1592 let key_slots = match &source.key {
1593 ListKey::Fields(key_fields) => key_fields
1594 .iter()
1595 .map(|field| KeySlot {
1596 field,
1597 slot: template
1598 .data
1599 .get_index_of(&field.path)
1600 .expect("BUG: key field is template data"),
1601 field_type: row_end_type(template, &RowEnd::Data(field.path.clone())),
1602 })
1603 .collect(),
1604 ListKey::Numbered => Box::default(),
1605 };
1606 Ok(ListRows {
1607 source,
1608 identity: Arc::new(source.identity.clone()),
1609 template,
1610 row_type,
1611 literals: Vec::with_capacity(raw_rows.len()),
1612 key_slots,
1613 keyed: Vec::new(),
1614 number: 0,
1615 raw_rows: raw_rows.into_iter(),
1616 })
1617 }
1618
1619 fn next_row(&mut self, mut rows: ListRows<'a>) -> Result<ResolveStep<'a>, Error> {
1620 let Some(raw_row) = rows.raw_rows.next() else {
1621 return Ok(ResolveStep::Return(ResolveOutput::Binding(
1622 self.finish_list(rows),
1623 std::mem::take(&mut self.overrides),
1624 )));
1625 };
1626 rows.number += 1;
1627 let Some(fields) = raw_row.into_map() else {
1628 return Err(request_error(
1629 &self.name,
1630 format!("row {} must be an object", rows.number),
1631 ));
1632 };
1633 let template = rows.template;
1634 let origin = crate::planning::semantics::numbered_row_origin(&self.list_key, rows.number);
1635 let row_key: Arc<str> = match &self.under {
1636 Some(under) => Arc::from(format!("{under}.{origin}")),
1637 None => Arc::from(origin),
1638 };
1639 self.rows = Some(rows);
1640 Ok(ResolveStep::Call(ResolveFrame::Level(LevelFrame::new(
1641 template,
1642 Some(row_key),
1643 fields,
1644 self.limits,
1645 ListLevel::Nested,
1646 ))))
1647 }
1648
1649 fn finish_row(
1651 &mut self,
1652 rows: &mut ListRows<'a>,
1653 row_data: EvaluateData,
1654 ) -> Result<Option<OperationResult>, Error> {
1655 let number = rows.number;
1656 let name = self.name.as_str();
1657 let mut bindings: Vec<Option<RowBinding>> = vec![None; rows.template.data.len()];
1658 if let Some(veto) = row_data
1659 .row_overrides
1660 .fields
1661 .values()
1662 .flat_map(IndexMap::values)
1663 .find_map(|binding| match binding {
1664 OperationResult::Veto(veto) => Some(veto),
1665 OperationResult::Value(_) => None,
1666 })
1667 {
1668 return Ok(Some(OperationResult::Veto(VetoType::computation(format!(
1669 "In {name} row {number}: {veto}"
1670 )))));
1671 }
1672 for (slot, binding) in row_data.bindings {
1673 if let OperationResult::Veto(veto) = &binding {
1674 return Ok(Some(OperationResult::Veto(VetoType::computation(format!(
1675 "In {name} row {number}: {veto}"
1676 )))));
1677 }
1678 bindings[slot] = Some(RowBinding::Bound(binding));
1679 }
1680 if let ListKey::Fields(_) = &rows.source.key {
1681 let mut values = Vec::with_capacity(rows.key_slots.len());
1682 for KeySlot {
1683 field: key_field,
1684 slot,
1685 field_type,
1686 } in &rows.key_slots
1687 {
1688 let key_path = &key_field.path;
1689 let value = match bindings.get(*slot).and_then(Option::as_ref) {
1690 Some(RowBinding::Bound(OperationResult::Value(bound))) => {
1691 typed_from_bound(bound, field_type)
1692 }
1693 Some(RowBinding::Bound(OperationResult::Veto(_))) => {
1694 unreachable!("BUG: vetoed row binding returned above")
1695 }
1696 Some(
1697 RowBinding::Deferred { .. }
1698 | RowBinding::Rows { .. }
1699 | RowBinding::Instance(_),
1700 ) => {
1701 unreachable!("BUG: evaluation data rows bind resolved values")
1702 }
1703 None => default_of(&rows.template.data, key_path).ok_or_else(|| {
1704 request_error(
1705 name,
1706 format!(
1707 "row {number} has no value for key field '{}'",
1708 key_field.name
1709 ),
1710 )
1711 })?,
1712 };
1713 values.push(value);
1714 }
1715 rows.keyed.push(values);
1716 }
1717 let literal = row_literal(
1718 Arc::clone(&rows.identity),
1719 RowOrigin::Numbered {
1720 list: self.list_key.clone(),
1721 number,
1722 under: self.under.clone(),
1723 },
1724 bindings,
1725 Arc::clone(&rows.row_type),
1726 );
1727 let ValueKind::Spec(SpecValue::Row(key)) = &literal.value else {
1728 unreachable!("BUG: a row literal is a row value");
1729 };
1730 self.overrides.extend(row_data.row_overrides.under(key));
1731 rows.literals.push(literal);
1732 Ok(None)
1733 }
1734
1735 fn finish_list(&self, rows: ListRows<'a>) -> OperationResult {
1736 let literals: Arc<[TypedLiteral]> = rows.literals.into();
1737 let list = match &rows.source.key {
1738 ListKey::Fields(key_fields) => {
1739 let field_names: Vec<String> = key_fields
1740 .iter()
1741 .map(|key_field| key_field.name.clone())
1742 .collect();
1743 match KeyIndex::build_numbered(field_names, &rows.keyed) {
1744 Ok(index) => ListValue::indexed(literals, Arc::new(index)),
1745 Err(conflict) => {
1746 return OperationResult::Veto(VetoType::computation(format!(
1747 "{}: {conflict}",
1748 self.name
1749 )));
1750 }
1751 }
1752 }
1753 ListKey::Numbered => ListValue::from(literals),
1754 };
1755 OperationResult::from_literal(LiteralValue {
1756 value: ValueKind::List(list),
1757 })
1758 }
1759}
1760
1761#[cfg(test)]
1762mod tests {
1763 use super::*;
1764 use crate::computation::rational::{decimal_to_rational, rational_new, rational_one};
1765 use crate::planning::semantics::{
1766 primitive_number_arc, MeasureUnit, MeasureUnits, RatioUnit, RatioUnits, TypeExtends,
1767 };
1768 use rust_decimal::Decimal;
1769
1770 fn dummy_source() -> Source {
1771 Source::new(
1772 crate::parsing::source::SourceType::Volatile,
1773 crate::parsing::ast::Span {
1774 start: 0,
1775 end: 0,
1776 line: 1,
1777 col: 1,
1778 },
1779 )
1780 }
1781
1782 fn mass_measure_type() -> Arc<LemmaType> {
1783 Arc::new(LemmaType::new(
1784 "Mass".to_string(),
1785 TypeSpecification::Measure {
1786 minimum: None,
1787 maximum: None,
1788 units: MeasureUnits::from(vec![
1789 MeasureUnit {
1790 name: "kilogram".to_string(),
1791 scale: crate::literals::UnitScale::Literal(rational_one()),
1792 derived_measure_factors: Vec::new(),
1793 decomposition: crate::literals::BaseMeasureVector::new(),
1794 minimum: None,
1795 maximum: None,
1796 suggestion_magnitude: None,
1797 },
1798 MeasureUnit {
1799 name: "gram".to_string(),
1800 scale: crate::literals::UnitScale::Literal(
1801 decimal_to_rational(Decimal::new(1, 3)).expect("factor"),
1802 ),
1803 derived_measure_factors: Vec::new(),
1804 decomposition: crate::literals::BaseMeasureVector::new(),
1805 minimum: None,
1806 maximum: None,
1807 suggestion_magnitude: None,
1808 },
1809 ]),
1810 traits: Vec::new(),
1811 decomposition: None,
1812 unit: None,
1813 help: String::new(),
1814 },
1815 TypeExtends::Primitive,
1816 ))
1817 }
1818
1819 fn ratio_with_percent_type() -> Arc<LemmaType> {
1820 Arc::new(LemmaType::new(
1821 "Rate".to_string(),
1822 TypeSpecification::Ratio {
1823 minimum: None,
1824 maximum: None,
1825 units: RatioUnits::from(vec![
1826 RatioUnit {
1827 name: "percent".to_string(),
1828 value: decimal_to_rational(Decimal::new(100, 0)).expect("factor"),
1829 minimum: None,
1830 maximum: None,
1831 suggestion_magnitude: None,
1832 },
1833 RatioUnit {
1834 name: "fraction".to_string(),
1835 value: rational_one(),
1836 minimum: None,
1837 maximum: None,
1838 suggestion_magnitude: None,
1839 },
1840 ]),
1841 unit: None,
1842 help: String::new(),
1843 },
1844 TypeExtends::Primitive,
1845 ))
1846 }
1847
1848 #[test]
1849 fn string_input_parsed_against_type() {
1850 let ty = primitive_number_arc();
1851 let lit = parse_data_value(
1852 &EvaluateDataValue::Literal("42".to_string()),
1853 ty,
1854 &dummy_source(),
1855 &UnitIndex::new(),
1856 &IndexMap::new(),
1857 )
1858 .unwrap();
1859 assert!(matches!(lit.value, ValueKind::Number(_)));
1860 }
1861
1862 fn unit_map(unit: &str, magnitude: &str) -> EvaluateDataValue {
1863 let mut map = IndexMap::new();
1864 map.insert(
1865 unit.to_string(),
1866 EvaluateDataValue::Literal(magnitude.into()),
1867 );
1868 EvaluateDataValue::Map(map)
1869 }
1870
1871 #[test]
1872 fn single_unit_measure_map_parses() {
1873 let ty = mass_measure_type();
1874 let lit = parse_data_value(
1875 &unit_map("kilogram", "2"),
1876 &ty,
1877 &dummy_source(),
1878 &UnitIndex::new(),
1879 &IndexMap::new(),
1880 )
1881 .unwrap();
1882 let ValueKind::Measure(magnitude) = &lit.value else {
1883 panic!("expected measure");
1884 };
1885 assert_eq!(magnitude, &rational_new(2, 1));
1886 }
1887
1888 #[test]
1889 fn single_unit_ratio_map_parses() {
1890 let ty = ratio_with_percent_type();
1891 let lit = parse_data_value(
1892 &unit_map("percent", "5"),
1893 &ty,
1894 &dummy_source(),
1895 &UnitIndex::new(),
1896 &IndexMap::new(),
1897 )
1898 .unwrap();
1899 let ValueKind::Ratio(canonical) = &lit.value else {
1900 panic!("expected ratio");
1901 };
1902 assert_eq!(
1903 *canonical,
1904 decimal_to_rational(Decimal::new(5, 2)).expect("canonical")
1905 );
1906 }
1907
1908 #[test]
1909 fn multi_key_map_is_shape_error() {
1910 let mut map = IndexMap::new();
1911 map.insert(
1912 "kilogram".to_string(),
1913 EvaluateDataValue::Literal("2".to_string()),
1914 );
1915 map.insert(
1916 "gram".to_string(),
1917 EvaluateDataValue::Literal("2000".into()),
1918 );
1919 let message = evaluate_data_shape_error(&EvaluateDataValue::Map(map), &mass_measure_type())
1920 .expect("shape");
1921 assert!(message.contains("single-unit"), "{message}");
1922 }
1923
1924 #[test]
1925 fn empty_map_is_shape_error() {
1926 let message = evaluate_data_shape_error(
1927 &EvaluateDataValue::Map(IndexMap::new()),
1928 &mass_measure_type(),
1929 )
1930 .expect("shape");
1931 assert!(message.contains("single-unit"), "{message}");
1932 }
1933
1934 #[test]
1935 fn map_on_text_is_shape_error() {
1936 let message = evaluate_data_shape_error(
1937 &unit_map("eur", "84"),
1938 &LemmaType::primitive(TypeSpecification::Text {
1939 length: None,
1940 help: String::new(),
1941 }),
1942 )
1943 .expect("shape");
1944 assert!(message.contains("text"), "{message}");
1945 }
1946
1947 #[test]
1948 fn list_on_scalar_is_shape_error() {
1949 let message = evaluate_data_shape_error(
1950 &EvaluateDataValue::List(vec![EvaluateDataValue::Literal("1".to_string())]),
1951 &LemmaType::primitive(TypeSpecification::Number {
1952 minimum: None,
1953 maximum: None,
1954 decimals: None,
1955 help: String::new(),
1956 }),
1957 )
1958 .expect("shape");
1959 assert!(message.contains("array"), "{message}");
1960 }
1961
1962 #[test]
1963 fn boolean_on_number_is_shape_error() {
1964 let message = evaluate_data_shape_error(
1965 &EvaluateDataValue::Boolean(true),
1966 &LemmaType::primitive(TypeSpecification::Number {
1967 minimum: None,
1968 maximum: None,
1969 decimals: None,
1970 help: String::new(),
1971 }),
1972 )
1973 .expect("shape");
1974 assert!(message.contains("boolean"), "{message}");
1975 }
1976
1977 fn mass_list_type() -> LemmaType {
1978 LemmaType::list_of(mass_measure_type())
1979 }
1980
1981 #[test]
1982 fn nested_boolean_item_is_shape_error() {
1983 let message = evaluate_data_shape_error(
1984 &EvaluateDataValue::List(vec![
1985 EvaluateDataValue::Literal("1 kilogram".to_string()),
1986 EvaluateDataValue::Boolean(true),
1987 ]),
1988 &mass_list_type(),
1989 )
1990 .expect("shape");
1991 assert!(
1992 message.starts_with("list item 2: boolean input is only valid for boolean data"),
1993 "{message}"
1994 );
1995 }
1996
1997 #[test]
1998 fn nested_map_item_is_shape_error() {
1999 let text_list =
2000 LemmaType::list_of(Arc::new(LemmaType::primitive(TypeSpecification::Text {
2001 length: None,
2002 help: String::new(),
2003 })));
2004 let message = evaluate_data_shape_error(
2005 &EvaluateDataValue::List(vec![unit_map("eur", "84")]),
2006 &text_list,
2007 )
2008 .expect("shape");
2009 assert!(
2010 message.starts_with("list item 1: object overlay"),
2011 "{message}"
2012 );
2013 }
2014
2015 #[test]
2016 fn nested_list_item_is_shape_error() {
2017 let message = evaluate_data_shape_error(
2018 &EvaluateDataValue::List(vec![EvaluateDataValue::List(vec![
2019 EvaluateDataValue::Literal("1 eur".to_string()),
2020 ])]),
2021 &mass_list_type(),
2022 )
2023 .expect("shape");
2024 assert!(
2025 message.starts_with("list item 1: array overlay"),
2026 "{message}"
2027 );
2028 }
2029
2030 #[test]
2031 fn deeply_nested_item_reports_full_item_path() {
2032 let nested = LemmaType::list_of(Arc::new(mass_list_type()));
2033 let message = evaluate_data_shape_error(
2034 &EvaluateDataValue::List(vec![
2035 EvaluateDataValue::List(vec![EvaluateDataValue::Literal("1 kilogram".to_string())]),
2036 EvaluateDataValue::List(vec![EvaluateDataValue::Boolean(true)]),
2037 ]),
2038 &nested,
2039 )
2040 .expect("shape");
2041 assert!(
2042 message
2043 .starts_with("list item 2, item 1: boolean input is only valid for boolean data"),
2044 "{message}"
2045 );
2046 }
2047
2048 #[test]
2049 fn nested_literal_under_list_type_is_shape_error() {
2050 let nested = LemmaType::list_of(Arc::new(mass_list_type()));
2051 let message = evaluate_data_shape_error(
2052 &EvaluateDataValue::List(vec![EvaluateDataValue::Literal("1 kilogram".to_string())]),
2053 &nested,
2054 )
2055 .expect("shape");
2056 assert!(
2057 message.starts_with("list item 1: literal overlay"),
2058 "{message}"
2059 );
2060 }
2061
2062 #[test]
2063 fn valid_nested_measure_list_has_no_shape_error() {
2064 let message = evaluate_data_shape_error(
2065 &EvaluateDataValue::List(vec![
2066 unit_map("kilogram", "2"),
2067 EvaluateDataValue::Literal("500 gram".to_string()),
2068 ]),
2069 &mass_list_type(),
2070 );
2071 assert!(message.is_none(), "{message:?}");
2072 }
2073
2074 fn load_engine(code: &str) -> crate::Engine {
2075 let mut engine = crate::Engine::new();
2076 engine
2077 .load([(crate::SourceType::Volatile, code.to_string())])
2078 .expect("load");
2079 engine
2080 }
2081
2082 fn ordered(pairs: &[(&str, EvaluateDataValue)]) -> IndexMap<String, EvaluateDataValue> {
2083 pairs
2084 .iter()
2085 .map(|(k, v)| (k.to_string(), v.clone()))
2086 .collect()
2087 }
2088
2089 fn sample_shallow() -> EvaluateDataValue {
2090 EvaluateDataValue::Map(ordered(&[
2091 (
2092 "price",
2093 EvaluateDataValue::Map(ordered(&[("eur", EvaluateDataValue::from("84"))])),
2094 ),
2095 (
2096 "fares",
2097 EvaluateDataValue::List(vec![
2098 EvaluateDataValue::from("89 eur"),
2099 EvaluateDataValue::Boolean(true),
2100 EvaluateDataValue::Map(IndexMap::new()),
2101 EvaluateDataValue::List(Vec::new()),
2102 ]),
2103 ),
2104 ]))
2105 }
2106
2107 #[test]
2108 fn debug_matches_derived_layout_compact_and_pretty() {
2109 let value = sample_shallow();
2110 assert_eq!(
2111 format!("{value:?}"),
2112 r#"Map({"price": Map({"eur": Literal("84")}), "fares": List([Literal("89 eur"), Boolean(true), Map({}), List([])])})"#
2113 );
2114 assert_eq!(
2115 format!("{value:#?}"),
2116 concat!(
2117 "Map(\n",
2118 " {\n",
2119 " \"price\": Map(\n",
2120 " {\n",
2121 " \"eur\": Literal(\n",
2122 " \"84\",\n",
2123 " ),\n",
2124 " },\n",
2125 " ),\n",
2126 " \"fares\": List(\n",
2127 " [\n",
2128 " Literal(\n",
2129 " \"89 eur\",\n",
2130 " ),\n",
2131 " Boolean(\n",
2132 " true,\n",
2133 " ),\n",
2134 " Map(\n",
2135 " {},\n",
2136 " ),\n",
2137 " List(\n",
2138 " [],\n",
2139 " ),\n",
2140 " ],\n",
2141 " ),\n",
2142 " },\n",
2143 ")",
2144 )
2145 );
2146 }
2147
2148 #[test]
2149 fn deep_chains_clone_eq_debug_and_drop_on_small_stack() {
2150 const DEPTH: usize = 1_000_000;
2151 std::thread::Builder::new()
2152 .stack_size(256 * 1024)
2153 .spawn(|| {
2154 let mut list = EvaluateDataValue::Literal("leaf".to_string());
2155 for _ in 0..DEPTH {
2156 list = EvaluateDataValue::List(vec![list]);
2157 }
2158 let cloned = list.clone();
2159 assert!(list == cloned);
2160 let rendered = format!("{list:?}");
2161 assert_eq!(
2162 rendered.len(),
2163 DEPTH * "List([".len() + "Literal(\"leaf\")".len() + DEPTH * "])".len()
2164 );
2165 drop(cloned);
2166
2167 let mut map = EvaluateDataValue::Boolean(false);
2168 for _ in 0..DEPTH {
2169 let mut level = IndexMap::new();
2170 level.insert("n".to_string(), map);
2171 map = EvaluateDataValue::Map(level);
2172 }
2173 let cloned = map.clone();
2174 assert!(map == cloned);
2175 assert!(map != list);
2176 drop(cloned);
2177 drop(map);
2178 drop(list);
2179 })
2180 .expect("spawn small-stack thread")
2181 .join()
2182 .expect("deep run data must not overflow the stack");
2183 }
2184
2185 #[test]
2186 fn eq_is_order_insensitive_for_maps_and_ordered_for_lists() {
2187 let forward = EvaluateDataValue::Map(ordered(&[
2188 ("a", EvaluateDataValue::from("1")),
2189 ("b", EvaluateDataValue::from("2")),
2190 ]));
2191 let reversed = EvaluateDataValue::Map(ordered(&[
2192 ("b", EvaluateDataValue::from("2")),
2193 ("a", EvaluateDataValue::from("1")),
2194 ]));
2195 assert!(forward == reversed);
2196 assert!(
2197 EvaluateDataValue::List(vec![
2198 EvaluateDataValue::from("1"),
2199 EvaluateDataValue::from("2")
2200 ]) != EvaluateDataValue::List(vec![
2201 EvaluateDataValue::from("2"),
2202 EvaluateDataValue::from("1")
2203 ])
2204 );
2205 assert!(EvaluateDataValue::from("1") != EvaluateDataValue::Boolean(true));
2206 }
2207
2208 #[test]
2209 fn request_error_names_first_key_in_caller_order() {
2210 let engine = load_engine(
2211 r#"
2212spec inner
2213data n: 1
2214
2215spec s
2216uses left: inner
2217uses right: inner
2218rule out: 1
2219"#,
2220 );
2221 let now = crate::DateTimeValue::now();
2222 let overlay = EvaluateDataValue::Map(ordered(&[("n", EvaluateDataValue::from("2"))]));
2223 let left_first = engine
2224 .evaluate(
2225 None,
2226 "s",
2227 Some(&now),
2228 ordered(&[("left", overlay.clone()), ("right", overlay.clone())]),
2229 None,
2230 false,
2231 )
2232 .expect_err("uses alias map is a request error");
2233 assert!(left_first.to_string().contains("'left'"), "{left_first}");
2234 assert!(!left_first.to_string().contains("'right'"), "{left_first}");
2235
2236 let right_first = engine
2237 .evaluate(
2238 None,
2239 "s",
2240 Some(&now),
2241 ordered(&[("right", overlay.clone()), ("left", overlay)]),
2242 None,
2243 false,
2244 )
2245 .expect_err("uses alias map is a request error");
2246 assert!(right_first.to_string().contains("'right'"), "{right_first}");
2247 assert!(!right_first.to_string().contains("'left'"), "{right_first}");
2248 }
2249
2250 fn roster_engine() -> crate::Engine {
2251 load_engine(
2252 r#"
2253spec person
2254data age: number
2255 -> minimum 0
2256data score: number
2257 -> minimum 0
2258
2259spec roster
2260uses person
2261data people: person list
2262rule n: people -> count
2263"#,
2264 )
2265 }
2266
2267 #[test]
2268 fn row_veto_names_first_field_in_caller_order() {
2269 let engine = roster_engine();
2270 let now = crate::DateTimeValue::now();
2271 let age_first = engine
2272 .evaluate(
2273 None,
2274 "roster",
2275 Some(&now),
2276 ordered(&[(
2277 "people",
2278 EvaluateDataValue::List(vec![EvaluateDataValue::Map(ordered(&[
2279 ("age", EvaluateDataValue::from("-1")),
2280 ("score", EvaluateDataValue::from("-1")),
2281 ]))]),
2282 )]),
2283 Some(&["n".to_string()]),
2284 false,
2285 )
2286 .expect("evaluate");
2287 let reason = age_first
2288 .results
2289 .get("n")
2290 .expect("n")
2291 .veto_reason
2292 .as_deref()
2293 .expect("veto");
2294 assert!(reason.contains("age"), "{reason}");
2295 assert!(!reason.contains("score"), "{reason}");
2296
2297 let score_first = engine
2298 .evaluate(
2299 None,
2300 "roster",
2301 Some(&now),
2302 ordered(&[(
2303 "people",
2304 EvaluateDataValue::List(vec![EvaluateDataValue::Map(ordered(&[
2305 ("score", EvaluateDataValue::from("-1")),
2306 ("age", EvaluateDataValue::from("-1")),
2307 ]))]),
2308 )]),
2309 Some(&["n".to_string()]),
2310 false,
2311 )
2312 .expect("evaluate");
2313 let reason = score_first
2314 .results
2315 .get("n")
2316 .expect("n")
2317 .veto_reason
2318 .as_deref()
2319 .expect("veto");
2320 assert!(reason.contains("score"), "{reason}");
2321 assert!(!reason.contains("age"), "{reason}");
2322 }
2323
2324 #[test]
2325 fn row_unknown_fields_are_ignored() {
2326 let engine = roster_engine();
2327 let now = crate::DateTimeValue::now();
2328 let bogus_first = engine
2329 .evaluate(
2330 None,
2331 "roster",
2332 Some(&now),
2333 ordered(&[(
2334 "people",
2335 EvaluateDataValue::List(vec![EvaluateDataValue::Map(ordered(&[
2336 ("bogus", EvaluateDataValue::from("1")),
2337 ("also", EvaluateDataValue::from("2")),
2338 ("age", EvaluateDataValue::from("1")),
2339 ("score", EvaluateDataValue::from("1")),
2340 ]))]),
2341 )]),
2342 None,
2343 false,
2344 )
2345 .expect("unknown row fields are ignored");
2346 assert!(bogus_first.results.get("n").is_some());
2347
2348 let also_first = engine
2349 .evaluate(
2350 None,
2351 "roster",
2352 Some(&now),
2353 ordered(&[(
2354 "people",
2355 EvaluateDataValue::List(vec![EvaluateDataValue::Map(ordered(&[
2356 ("also", EvaluateDataValue::from("2")),
2357 ("bogus", EvaluateDataValue::from("1")),
2358 ("age", EvaluateDataValue::from("1")),
2359 ("score", EvaluateDataValue::from("1")),
2360 ]))]),
2361 )]),
2362 None,
2363 false,
2364 )
2365 .expect("unknown row fields are ignored");
2366 assert!(also_first.results.get("n").is_some());
2367 }
2368
2369 fn tags_engine(limits: crate::ResourceLimits) -> crate::Engine {
2370 let mut engine = crate::Engine::with_limits(limits);
2371 engine
2372 .load([(
2373 crate::SourceType::Volatile,
2374 "spec tagged\ndata tags: text list\nrule n: tags -> count".to_string(),
2375 )])
2376 .expect("load");
2377 engine
2378 }
2379
2380 fn evaluate_tags(engine: &crate::Engine, tags: Vec<String>) -> crate::Response {
2381 let now = crate::DateTimeValue::now();
2382 engine
2383 .evaluate(
2384 None,
2385 "tagged",
2386 Some(&now),
2387 ordered(&[(
2388 "tags",
2389 EvaluateDataValue::List(
2390 tags.into_iter().map(EvaluateDataValue::Literal).collect(),
2391 ),
2392 )]),
2393 Some(&["n".to_string()]),
2394 false,
2395 )
2396 .expect("evaluate")
2397 }
2398
2399 #[test]
2400 fn value_list_over_value_byte_limit_binds() {
2401 let engine = tags_engine(crate::ResourceLimits::default());
2402 let limit = crate::ResourceLimits::default().max_data_value_bytes;
2403 let tags: Vec<String> = (0..200).map(|i| format!("customer-tag-{i:04}")).collect();
2404 let total: usize = tags.iter().map(String::len).sum();
2405 assert!(
2406 total > limit,
2407 "fixture must exceed {limit} bytes, has {total}"
2408 );
2409
2410 let response = evaluate_tags(&engine, tags);
2411 let result = response.results.get("n").expect("n");
2412 assert_eq!(result.veto_reason, None);
2413 assert_eq!(result.result(), Some("200"));
2414 }
2415
2416 #[test]
2417 fn value_list_item_over_value_byte_limit_vetoes() {
2418 let engine = tags_engine(crate::ResourceLimits {
2419 max_data_value_bytes: 50,
2420 ..crate::ResourceLimits::default()
2421 });
2422 let response = evaluate_tags(
2423 &engine,
2424 vec!["vip".to_string(), "x".repeat(51), "trial".to_string()],
2425 );
2426 let reason = response
2427 .results
2428 .get("n")
2429 .expect("n")
2430 .veto_reason
2431 .as_deref()
2432 .expect("veto");
2433 assert!(
2434 reason.contains("list item 2 exceeds the size limit."),
2435 "{reason}"
2436 );
2437 }
2438}