1use crate::computation::{OperationResult, VetoType};
2use crate::planning::execution_plan::{validate_value_against_type, ExecutionPlan};
3use crate::planning::instance_list::{
4 key_conflict, row_end, row_end_type, template_default, typed_from_bound, InstanceListSource,
5 KeyedRow,
6};
7use crate::planning::normalize::lookup_data_definition;
8use crate::planning::semantics::{
9 parse_value_from_string, parser_value_to_value_kind, row_literal, DataDefinition, DataPath,
10 ImportOverlay, LemmaType, ListKey, LiteralValue, ReferenceTarget, RowEnd, RowOrigin, Source,
11 TypeSpecification, 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 = crate::parsing::ast::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),
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: &crate::parsing::ast::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.specifications, 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: &crate::parsing::ast::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)]
742pub struct EvaluateData {
743 pub bindings: IndexMap<DataPath, OperationResult>,
745 pub ignored_unknown: Vec<String>,
747}
748
749impl EvaluateData {
750 pub fn resolve(
756 plan: &ExecutionPlan,
757 raw_values: IndexMap<String, EvaluateDataValue>,
758 limits: &ResourceLimits,
759 ) -> Result<Self, Error> {
760 Self::resolve_level(plan, raw_values, limits, ListLevel::Top)
761 }
762
763 fn resolve_level(
764 plan: &ExecutionPlan,
765 raw_values: IndexMap<String, EvaluateDataValue>,
766 limits: &ResourceLimits,
767 level: ListLevel,
768 ) -> Result<Self, Error> {
769 match run_resolve(ResolveFrame::Level(LevelFrame::new(
770 plan, raw_values, limits, level,
771 )))? {
772 ResolveOutput::Level(evaluate_data) => Ok(evaluate_data),
773 ResolveOutput::Binding(_) => {
774 panic!("BUG: root level frame returned a list binding")
775 }
776 }
777 }
778}
779
780enum ResolveOutput {
781 Level(EvaluateData),
782 Binding(OperationResult),
783}
784
785enum ResolveStep<'a> {
786 Call(ResolveFrame<'a>),
787 Return(ResolveOutput),
788}
789
790enum ResolveFrame<'a> {
791 Level(LevelFrame<'a>),
792 List(ListFrame<'a>),
793}
794
795impl<'a> ResolveFrame<'a> {
796 fn resume(&mut self, input: Option<ResolveOutput>) -> Result<ResolveStep<'a>, Error> {
797 match self {
798 ResolveFrame::Level(frame) => frame.resume(input),
799 ResolveFrame::List(frame) => frame.resume(input),
800 }
801 }
802}
803
804fn run_resolve(root: ResolveFrame<'_>) -> Result<ResolveOutput, Error> {
805 let mut frames = vec![root];
806 let mut input = None;
807 loop {
808 let frame = frames.last_mut().expect("BUG: resolve frame stack empty");
809 match frame.resume(input.take())? {
810 ResolveStep::Call(child) => frames.push(child),
811 ResolveStep::Return(output) => {
812 frames.pop();
813 if frames.is_empty() {
814 return Ok(output);
815 }
816 input = Some(output);
817 }
818 }
819 }
820}
821
822struct LevelFrame<'a> {
825 plan: &'a ExecutionPlan,
826 limits: &'a ResourceLimits,
827 level: ListLevel,
828 entries: indexmap::map::IntoIter<String, EvaluateDataValue>,
829 seen_canonical: HashSet<String>,
830 evaluate_data: EvaluateData,
831 awaiting: Option<&'a DataPath>,
832}
833
834impl<'a> LevelFrame<'a> {
835 fn new(
836 plan: &'a ExecutionPlan,
837 raw_values: IndexMap<String, EvaluateDataValue>,
838 limits: &'a ResourceLimits,
839 level: ListLevel,
840 ) -> Self {
841 let capacity = raw_values.len();
842 Self {
843 plan,
844 limits,
845 level,
846 entries: raw_values.into_iter(),
847 seen_canonical: HashSet::with_capacity(capacity),
848 evaluate_data: EvaluateData {
849 bindings: IndexMap::with_capacity(capacity),
850 ..EvaluateData::default()
851 },
852 awaiting: None,
853 }
854 }
855
856 fn resume(&mut self, input: Option<ResolveOutput>) -> Result<ResolveStep<'a>, Error> {
857 match (self.awaiting.take(), input) {
858 (None, None) => {}
859 (Some(path), Some(ResolveOutput::Binding(binding))) => {
860 self.evaluate_data.bindings.insert(path.clone(), binding);
861 }
862 _ => panic!("BUG: level frame resumed with an unexpected output"),
863 }
864 self.advance()
865 }
866
867 fn advance(&mut self) -> Result<ResolveStep<'a>, Error> {
868 let plan = self.plan;
869 let limits = self.limits;
870 let level = self.level;
871 let evaluate_data = &mut self.evaluate_data;
872 let seen_canonical = &mut self.seen_canonical;
873
874 for (name, raw_value) in self.entries.by_ref() {
875 let canonical = crate::parsing::ast::canonical_logical_name(&name);
876 if seen_canonical.contains(canonical.as_ref()) {
877 let canonical = canonical.into_owned();
878 return Err(Error::request(
879 format!("Duplicate data key '{canonical}'"),
880 Some("Data keys are case-insensitive; remove the duplicate"),
881 )
882 .with_related_data(name));
883 }
884 seen_canonical.insert(canonical.to_string());
885
886 let Some((data_path, data_definition)) = resolve_evaluate_data_target(plan, &canonical)
887 else {
888 if let Some(list) = instance_list_of_row_key(plan, &canonical) {
889 return Err(Error::request(
890 format!(
891 "data '{name}': rows of list '{list}' are supplied as one array under '{list}'"
892 ),
893 Some(format!("pass the whole list as '{list}'")),
894 )
895 .with_related_data(name));
896 }
897 evaluate_data.ignored_unknown.push(name);
898 continue;
899 };
900
901 if let Some(target) = overlay_reference_target(plan, data_path) {
902 return Err(Error::request(
903 format!(
904 "data '{name}' is bound to '{target}' by the spec and cannot be supplied"
905 ),
906 Some(format!("remove '{name}' from the evaluation data")),
907 )
908 .with_related_data(name));
909 }
910
911 let data_source = data_definition.source();
912 let type_arc = match data_definition {
913 DataDefinition::TypeDeclaration { resolved_type, .. }
914 | DataDefinition::Value { resolved_type, .. } => Arc::clone(resolved_type),
915 DataDefinition::Reference { target, .. } => {
916 let target = match target {
917 ReferenceTarget::Data(path) => path.input_key(),
918 ReferenceTarget::Rule(path) => path.input_key(),
919 };
920 return Err(Error::request(
921 format!(
922 "data '{name}' is bound to '{target}' by the spec and cannot be supplied"
923 ),
924 Some(format!("remove '{name}' from the evaluation data")),
925 )
926 .with_related_data(name));
927 }
928 DataDefinition::Import { .. } => {
929 if matches!(
930 raw_value,
931 EvaluateDataValue::Map(_) | EvaluateDataValue::List(_)
932 ) {
933 return Err(Error::request(
934 format!(
935 "data '{name}': '{name}' is a uses alias; objects and arrays are not a nesting level"
936 ),
937 Some(format!("bind fields with dotted keys, e.g. '{name}.<field>'")),
938 )
939 .with_related_data(name));
940 }
941 evaluate_data.ignored_unknown.push(name);
942 continue;
943 }
944 DataDefinition::InstanceList { .. } => {
945 self.awaiting = Some(data_path);
946 return Ok(ResolveStep::Call(ResolveFrame::List(ListFrame::new(
947 plan, data_path, name, raw_value, limits, level,
948 ))));
949 }
950 };
951
952 let input_key = canonical;
953
954 if raw_value.is_empty() && type_arc.empty_runtime_input_vetoes() {
955 evaluate_data.bindings.insert(
956 data_path.clone(),
957 OperationResult::Veto(VetoType::computation(
958 type_arc.data_veto_message(&input_key, "cannot be empty."),
959 )),
960 );
961 continue;
962 }
963
964 if let Some(message) = evaluate_data_shape_error(&raw_value, &type_arc) {
965 return Err(Error::request(
966 format!("data '{name}': {message}"),
967 Some(format!("fix the overlay for '{name}'")),
968 )
969 .with_related_data(name));
970 }
971
972 let typed = match parse_data_value(
973 &raw_value,
974 &type_arc,
975 data_source,
976 plan.expression_unit_index(),
977 &plan.resolved_types.resolved,
978 ) {
979 Ok(value) => value,
980 Err(error) => {
981 evaluate_data.bindings.insert(
982 data_path.clone(),
983 OperationResult::Veto(VetoType::computation(
984 type_arc.data_veto_message(&input_key, error.message()),
985 )),
986 );
987 continue;
988 }
989 };
990
991 if let ValueKind::List(rows) = &typed.value {
992 if rows.len() > limits.max_list_rows {
993 return Err(Error::request(
994 format!(
995 "data '{name}' has {} rows, over max_list_rows {}",
996 rows.len(),
997 limits.max_list_rows
998 ),
999 Some("reduce the list size or raise max_list_rows".to_string()),
1000 )
1001 .with_related_data(name));
1002 }
1003 }
1004
1005 let literal_value = LiteralValue {
1006 value: typed.value.clone(),
1007 };
1008 let oversize = match &typed.value {
1009 ValueKind::List(items) => items
1010 .iter()
1011 .position(|item| item.byte_size() > limits.max_data_value_bytes)
1012 .map(|index| format!("list item {} exceeds the size limit.", index + 1)),
1013 _ => (literal_value.byte_size() > limits.max_data_value_bytes)
1014 .then(|| "exceeds the size limit.".to_string()),
1015 };
1016 if let Some(reason) = oversize {
1017 evaluate_data.bindings.insert(
1018 data_path.clone(),
1019 OperationResult::Veto(VetoType::computation(
1020 type_arc.data_veto_message(&input_key, &reason),
1021 )),
1022 );
1023 continue;
1024 }
1025
1026 if let Err(message) = validate_value_against_type(
1027 typed.lemma_type.as_ref(),
1028 &literal_value,
1029 plan.expression_unit_index(),
1030 ) {
1031 evaluate_data.bindings.insert(
1032 data_path.clone(),
1033 OperationResult::Veto(VetoType::computation(
1034 type_arc.data_veto_message(&input_key, &message),
1035 )),
1036 );
1037 continue;
1038 }
1039
1040 evaluate_data.bindings.insert(
1041 data_path.clone(),
1042 OperationResult::from_bound(crate::planning::semantics::BoundValueKind {
1043 value: typed.value.clone(),
1044 unit: typed.lemma_type.unit().map(std::sync::Arc::from),
1045 }),
1046 );
1047 }
1048
1049 Ok(ResolveStep::Return(ResolveOutput::Level(std::mem::take(
1050 evaluate_data,
1051 ))))
1052 }
1053}
1054
1055fn resolve_evaluate_data_target<'a>(
1057 plan: &'a ExecutionPlan,
1058 key: &str,
1059) -> Option<(&'a DataPath, &'a DataDefinition)> {
1060 let path = plan.input_key_index.get(key)?;
1061 let definition = lookup_data_definition(&plan.data, &plan.row_templates, path)
1062 .unwrap_or_else(|| panic!("BUG: indexed input '{key}' has no data definition"));
1063 Some((path, definition))
1064}
1065
1066fn overlay_reference_target(plan: &ExecutionPlan, path: &DataPath) -> Option<String> {
1068 let first = path.segments.first()?;
1069 let import_marker = DataPath::local(first.uses.clone());
1070 let DataDefinition::Import { overlay, .. } = plan.data.get(&import_marker)? else {
1071 return None;
1072 };
1073 let relative = DataPath {
1074 segments: path.segments[1..].to_vec(),
1075 data: path.data.clone(),
1076 };
1077 match overlay.get(&relative)? {
1078 ImportOverlay::Reference { target, .. } => Some(match target {
1079 ReferenceTarget::Data(target) => target.input_key(),
1080 ReferenceTarget::Rule(target) => target.input_key(),
1081 }),
1082 ImportOverlay::Literal(_) => None,
1083 }
1084}
1085
1086fn instance_list_of_row_key(plan: &ExecutionPlan, key: &str) -> Option<String> {
1088 let parts: Vec<&str> = key.split('.').collect();
1089 plan.instance_lists.keys().find_map(|path| {
1090 let list_parts: Vec<&str> = path.input_key_segments().collect();
1091 (parts.starts_with(&list_parts) && parts.len() > list_parts.len()).then(|| path.input_key())
1092 })
1093}
1094
1095#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1098enum ListLevel {
1099 Top,
1100 Nested,
1101}
1102
1103fn check_list_tree(
1108 template: &ExecutionPlan,
1109 raw_rows: &[EvaluateDataValue],
1110 limits: &ResourceLimits,
1111) -> Result<(), String> {
1112 let mut stack: Vec<(&ExecutionPlan, &EvaluateDataValue, usize)> =
1113 raw_rows.iter().map(|row| (template, row, 2)).collect();
1114 let mut nodes = 0usize;
1115 let mut deepest = 0usize;
1116 while let Some((row_template, raw_row, depth)) = stack.pop() {
1117 nodes += 1;
1118 if nodes > limits.max_list_nodes {
1119 return Err(format!(
1120 "has more than max_list_nodes {} rows across all levels",
1121 limits.max_list_nodes
1122 ));
1123 }
1124 deepest = deepest.max(depth);
1125 let EvaluateDataValue::Map(fields) = raw_row else {
1126 continue;
1127 };
1128 for (field, value) in fields {
1129 let canonical = crate::parsing::ast::ascii_lowercase_logical_name(field.clone());
1130 let Some(source) = row_template
1131 .input_key_index
1132 .get(canonical.as_str())
1133 .and_then(|path| row_template.instance_lists.get(path))
1134 else {
1135 continue;
1136 };
1137 let EvaluateDataValue::List(rows) = value else {
1138 continue;
1139 };
1140 let nested = row_template.row_template(&source.identity);
1141 stack.extend(rows.iter().map(|row| (nested, row, depth + 1)));
1142 }
1143 }
1144 if deepest > limits.max_list_depth {
1145 return Err(format!(
1146 "rows nest {deepest} levels deep counting the spec itself, over max_list_depth {}",
1147 limits.max_list_depth
1148 ));
1149 }
1150 Ok(())
1151}
1152
1153struct ListFrame<'a> {
1161 plan: &'a ExecutionPlan,
1162 path: &'a DataPath,
1163 name: String,
1164 limits: &'a ResourceLimits,
1165 level: ListLevel,
1166 raw_value: Option<EvaluateDataValue>,
1167 rows: Option<ListRows<'a>>,
1168}
1169
1170struct ListRows<'a> {
1171 source: &'a InstanceListSource,
1172 template: &'a ExecutionPlan,
1173 row_type: Arc<LemmaType>,
1174 raw_rows: std::vec::IntoIter<EvaluateDataValue>,
1175 number: usize,
1176 literals: Vec<TypedLiteral>,
1177 keyed: Vec<KeyedRow>,
1178}
1179
1180fn request_error(name: &str, message: String) -> Error {
1181 Error::request(format!("data '{name}': {message}"), None::<String>).with_related_data(name)
1182}
1183
1184impl<'a> ListFrame<'a> {
1185 fn new(
1186 plan: &'a ExecutionPlan,
1187 path: &'a DataPath,
1188 name: String,
1189 raw_value: EvaluateDataValue,
1190 limits: &'a ResourceLimits,
1191 level: ListLevel,
1192 ) -> Self {
1193 Self {
1194 plan,
1195 path,
1196 name,
1197 limits,
1198 level,
1199 raw_value: Some(raw_value),
1200 rows: None,
1201 }
1202 }
1203
1204 fn resume(&mut self, input: Option<ResolveOutput>) -> Result<ResolveStep<'a>, Error> {
1205 match self.rows.take() {
1206 None => {
1207 assert!(
1208 input.is_none(),
1209 "BUG: list frame started with a child output"
1210 );
1211 let raw_value = self
1212 .raw_value
1213 .take()
1214 .expect("BUG: list frame started twice");
1215 let rows = self.start(raw_value)?;
1216 self.next_row(rows)
1217 }
1218 Some(mut rows) => {
1219 let Some(ResolveOutput::Level(row_data)) = input else {
1220 panic!("BUG: list frame resumed without a row result");
1221 };
1222 match self.finish_row(&mut rows, row_data)? {
1223 Some(veto) => Ok(ResolveStep::Return(ResolveOutput::Binding(veto))),
1224 None => self.next_row(rows),
1225 }
1226 }
1227 }
1228 }
1229
1230 fn start(&self, raw_value: EvaluateDataValue) -> Result<ListRows<'a>, Error> {
1231 let plan = self.plan;
1232 let source = plan
1233 .instance_lists
1234 .get(self.path)
1235 .expect("BUG: instance list data without a source");
1236 let template = plan.row_template(&source.identity);
1237 let Some(raw_rows) = raw_value.into_list() else {
1238 return Err(request_error(
1239 &self.name,
1240 format!(
1241 "rows of a `{}` list are an array of objects",
1242 source.identity.spec
1243 ),
1244 ));
1245 };
1246 if raw_rows.len() > self.limits.max_list_rows {
1247 return Err(request_error(
1248 &self.name,
1249 format!(
1250 "has {} rows, over max_list_rows {}",
1251 raw_rows.len(),
1252 self.limits.max_list_rows
1253 ),
1254 ));
1255 }
1256 if self.level == ListLevel::Top {
1257 check_list_tree(template, &raw_rows, self.limits)
1258 .map_err(|message| request_error(&self.name, message))?;
1259 }
1260 let row_type = Arc::new(LemmaType::primitive(TypeSpecification::Spec {
1261 spec: source.identity.clone(),
1262 }));
1263 Ok(ListRows {
1264 source,
1265 template,
1266 row_type,
1267 literals: Vec::with_capacity(raw_rows.len()),
1268 keyed: Vec::new(),
1269 number: 0,
1270 raw_rows: raw_rows.into_iter(),
1271 })
1272 }
1273
1274 fn next_row(&mut self, mut rows: ListRows<'a>) -> Result<ResolveStep<'a>, Error> {
1275 let Some(raw_row) = rows.raw_rows.next() else {
1276 return Ok(ResolveStep::Return(ResolveOutput::Binding(
1277 self.finish_list(rows),
1278 )));
1279 };
1280 rows.number += 1;
1281 let Some(fields) = raw_row.into_map() else {
1282 return Err(request_error(
1283 &self.name,
1284 format!("row {} must be an object", rows.number),
1285 ));
1286 };
1287 let template = rows.template;
1288 self.rows = Some(rows);
1289 Ok(ResolveStep::Call(ResolveFrame::Level(LevelFrame::new(
1290 template,
1291 fields,
1292 self.limits,
1293 ListLevel::Nested,
1294 ))))
1295 }
1296
1297 fn finish_row(
1299 &self,
1300 rows: &mut ListRows<'a>,
1301 row_data: EvaluateData,
1302 ) -> Result<Option<OperationResult>, Error> {
1303 let number = rows.number;
1304 let name = self.name.as_str();
1305 let mut bindings = IndexMap::new();
1306 for (field, binding) in row_data.bindings {
1307 if let OperationResult::Veto(veto) = &binding {
1308 return Ok(Some(OperationResult::Veto(VetoType::computation(format!(
1309 "{name} row {number}: {veto}"
1310 )))));
1311 }
1312 bindings.insert(field, binding);
1313 }
1314 if let ListKey::Fields(key_fields) = &rows.source.key {
1315 let mut values = Vec::with_capacity(key_fields.len());
1316 for field in key_fields {
1317 let key_path = match row_end(rows.template, field) {
1318 Some(RowEnd::Data(key_path)) => key_path,
1319 other => panic!("BUG: key field `{field}` is not row data: {other:?}"),
1320 };
1321 let field_type = row_end_type(rows.template, &RowEnd::Data(key_path.clone()));
1322 let value = match bindings.get(&key_path) {
1323 Some(OperationResult::Value(bound)) => typed_from_bound(bound, &field_type),
1324 Some(OperationResult::Veto(_)) => {
1325 unreachable!("BUG: vetoed row binding returned above")
1326 }
1327 None => template_default(rows.template, &key_path).ok_or_else(|| {
1328 request_error(
1329 name,
1330 format!("row {number} has no value for key field '{field}'"),
1331 )
1332 })?,
1333 };
1334 values.push(value);
1335 }
1336 rows.keyed.push(KeyedRow {
1337 label: format!("row {number}"),
1338 values,
1339 });
1340 }
1341 rows.literals.push(row_literal(
1342 rows.source.identity.clone(),
1343 RowOrigin::Numbered {
1344 list: self.path.input_key(),
1345 number,
1346 },
1347 bindings,
1348 Arc::clone(&rows.row_type),
1349 ));
1350 Ok(None)
1351 }
1352
1353 fn finish_list(&self, rows: ListRows<'a>) -> OperationResult {
1354 if let ListKey::Fields(key_fields) = &rows.source.key {
1355 if let Some(conflict) = key_conflict(key_fields, &rows.keyed) {
1356 return OperationResult::Veto(VetoType::computation(format!(
1357 "{}: {conflict}",
1358 self.name
1359 )));
1360 }
1361 }
1362 OperationResult::from_literal(LiteralValue {
1363 value: ValueKind::List(rows.literals),
1364 })
1365 }
1366}
1367
1368#[cfg(test)]
1369mod tests {
1370 use super::*;
1371 use crate::computation::rational::{decimal_to_rational, rational_new, rational_one};
1372 use crate::planning::semantics::{
1373 primitive_number_arc, MeasureUnit, MeasureUnits, RatioUnit, RatioUnits, TypeExtends,
1374 };
1375 use rust_decimal::Decimal;
1376
1377 fn dummy_source() -> Source {
1378 Source::new(
1379 crate::parsing::source::SourceType::Volatile,
1380 crate::parsing::ast::Span {
1381 start: 0,
1382 end: 0,
1383 line: 1,
1384 col: 1,
1385 },
1386 )
1387 }
1388
1389 fn mass_measure_type() -> Arc<LemmaType> {
1390 Arc::new(LemmaType::new(
1391 "Mass".to_string(),
1392 TypeSpecification::Measure {
1393 minimum: None,
1394 maximum: None,
1395 decimals: None,
1396 units: MeasureUnits::from(vec![
1397 MeasureUnit {
1398 name: "kilogram".to_string(),
1399 factor: rational_one(),
1400 derived_measure_factors: Vec::new(),
1401 decomposition: crate::literals::BaseMeasureVector::new(),
1402 minimum: None,
1403 maximum: None,
1404 suggestion_magnitude: None,
1405 },
1406 MeasureUnit {
1407 name: "gram".to_string(),
1408 factor: decimal_to_rational(Decimal::new(1, 3)).expect("factor"),
1409 derived_measure_factors: Vec::new(),
1410 decomposition: crate::literals::BaseMeasureVector::new(),
1411 minimum: None,
1412 maximum: None,
1413 suggestion_magnitude: None,
1414 },
1415 ]),
1416 traits: Vec::new(),
1417 decomposition: None,
1418 unit: None,
1419 help: String::new(),
1420 },
1421 TypeExtends::Primitive,
1422 ))
1423 }
1424
1425 fn ratio_with_percent_type() -> Arc<LemmaType> {
1426 Arc::new(LemmaType::new(
1427 "Rate".to_string(),
1428 TypeSpecification::Ratio {
1429 minimum: None,
1430 maximum: None,
1431 decimals: None,
1432 units: RatioUnits::from(vec![
1433 RatioUnit {
1434 name: "percent".to_string(),
1435 value: decimal_to_rational(Decimal::new(100, 0)).expect("factor"),
1436 minimum: None,
1437 maximum: None,
1438 suggestion_magnitude: None,
1439 },
1440 RatioUnit {
1441 name: "fraction".to_string(),
1442 value: rational_one(),
1443 minimum: None,
1444 maximum: None,
1445 suggestion_magnitude: None,
1446 },
1447 ]),
1448 unit: None,
1449 help: String::new(),
1450 },
1451 TypeExtends::Primitive,
1452 ))
1453 }
1454
1455 #[test]
1456 fn string_input_parsed_against_type() {
1457 let ty = primitive_number_arc();
1458 let lit = parse_data_value(
1459 &EvaluateDataValue::Literal("42".to_string()),
1460 ty,
1461 &dummy_source(),
1462 &UnitIndex::new(),
1463 &IndexMap::new(),
1464 )
1465 .unwrap();
1466 assert!(matches!(lit.value, ValueKind::Number(_)));
1467 }
1468
1469 fn unit_map(unit: &str, magnitude: &str) -> EvaluateDataValue {
1470 let mut map = IndexMap::new();
1471 map.insert(
1472 unit.to_string(),
1473 EvaluateDataValue::Literal(magnitude.into()),
1474 );
1475 EvaluateDataValue::Map(map)
1476 }
1477
1478 #[test]
1479 fn single_unit_measure_map_parses() {
1480 let ty = mass_measure_type();
1481 let lit = parse_data_value(
1482 &unit_map("kilogram", "2"),
1483 &ty,
1484 &dummy_source(),
1485 &UnitIndex::new(),
1486 &IndexMap::new(),
1487 )
1488 .unwrap();
1489 let ValueKind::Measure(magnitude) = &lit.value else {
1490 panic!("expected measure");
1491 };
1492 assert_eq!(magnitude, &rational_new(2, 1));
1493 }
1494
1495 #[test]
1496 fn single_unit_ratio_map_parses() {
1497 let ty = ratio_with_percent_type();
1498 let lit = parse_data_value(
1499 &unit_map("percent", "5"),
1500 &ty,
1501 &dummy_source(),
1502 &UnitIndex::new(),
1503 &IndexMap::new(),
1504 )
1505 .unwrap();
1506 let ValueKind::Ratio(canonical) = &lit.value else {
1507 panic!("expected ratio");
1508 };
1509 assert_eq!(
1510 *canonical,
1511 decimal_to_rational(Decimal::new(5, 2)).expect("canonical")
1512 );
1513 }
1514
1515 #[test]
1516 fn multi_key_map_is_shape_error() {
1517 let mut map = IndexMap::new();
1518 map.insert(
1519 "kilogram".to_string(),
1520 EvaluateDataValue::Literal("2".to_string()),
1521 );
1522 map.insert(
1523 "gram".to_string(),
1524 EvaluateDataValue::Literal("2000".into()),
1525 );
1526 let message = evaluate_data_shape_error(&EvaluateDataValue::Map(map), &mass_measure_type())
1527 .expect("shape");
1528 assert!(message.contains("single-unit"), "{message}");
1529 }
1530
1531 #[test]
1532 fn empty_map_is_shape_error() {
1533 let message = evaluate_data_shape_error(
1534 &EvaluateDataValue::Map(IndexMap::new()),
1535 &mass_measure_type(),
1536 )
1537 .expect("shape");
1538 assert!(message.contains("single-unit"), "{message}");
1539 }
1540
1541 #[test]
1542 fn map_on_text_is_shape_error() {
1543 let message = evaluate_data_shape_error(
1544 &unit_map("eur", "84"),
1545 &LemmaType::primitive(TypeSpecification::Text {
1546 length: None,
1547 options: Vec::new(),
1548 help: String::new(),
1549 }),
1550 )
1551 .expect("shape");
1552 assert!(message.contains("text"), "{message}");
1553 }
1554
1555 #[test]
1556 fn list_on_scalar_is_shape_error() {
1557 let message = evaluate_data_shape_error(
1558 &EvaluateDataValue::List(vec![EvaluateDataValue::Literal("1".to_string())]),
1559 &LemmaType::primitive(TypeSpecification::Number {
1560 minimum: None,
1561 maximum: None,
1562 decimals: None,
1563 help: String::new(),
1564 }),
1565 )
1566 .expect("shape");
1567 assert!(message.contains("array"), "{message}");
1568 }
1569
1570 #[test]
1571 fn boolean_on_number_is_shape_error() {
1572 let message = evaluate_data_shape_error(
1573 &EvaluateDataValue::Boolean(true),
1574 &LemmaType::primitive(TypeSpecification::Number {
1575 minimum: None,
1576 maximum: None,
1577 decimals: None,
1578 help: String::new(),
1579 }),
1580 )
1581 .expect("shape");
1582 assert!(message.contains("boolean"), "{message}");
1583 }
1584
1585 fn mass_list_type() -> LemmaType {
1586 LemmaType::list_of(mass_measure_type())
1587 }
1588
1589 #[test]
1590 fn nested_boolean_item_is_shape_error() {
1591 let message = evaluate_data_shape_error(
1592 &EvaluateDataValue::List(vec![
1593 EvaluateDataValue::Literal("1 kilogram".to_string()),
1594 EvaluateDataValue::Boolean(true),
1595 ]),
1596 &mass_list_type(),
1597 )
1598 .expect("shape");
1599 assert!(
1600 message.starts_with("list item 2: boolean input is only valid for boolean data"),
1601 "{message}"
1602 );
1603 }
1604
1605 #[test]
1606 fn nested_map_item_is_shape_error() {
1607 let text_list =
1608 LemmaType::list_of(Arc::new(LemmaType::primitive(TypeSpecification::Text {
1609 length: None,
1610 options: Vec::new(),
1611 help: String::new(),
1612 })));
1613 let message = evaluate_data_shape_error(
1614 &EvaluateDataValue::List(vec![unit_map("eur", "84")]),
1615 &text_list,
1616 )
1617 .expect("shape");
1618 assert!(
1619 message.starts_with("list item 1: object overlay"),
1620 "{message}"
1621 );
1622 }
1623
1624 #[test]
1625 fn nested_list_item_is_shape_error() {
1626 let message = evaluate_data_shape_error(
1627 &EvaluateDataValue::List(vec![EvaluateDataValue::List(vec![
1628 EvaluateDataValue::Literal("1 eur".to_string()),
1629 ])]),
1630 &mass_list_type(),
1631 )
1632 .expect("shape");
1633 assert!(
1634 message.starts_with("list item 1: array overlay"),
1635 "{message}"
1636 );
1637 }
1638
1639 #[test]
1640 fn deeply_nested_item_reports_full_item_path() {
1641 let nested = LemmaType::list_of(Arc::new(mass_list_type()));
1642 let message = evaluate_data_shape_error(
1643 &EvaluateDataValue::List(vec![
1644 EvaluateDataValue::List(vec![EvaluateDataValue::Literal("1 kilogram".to_string())]),
1645 EvaluateDataValue::List(vec![EvaluateDataValue::Boolean(true)]),
1646 ]),
1647 &nested,
1648 )
1649 .expect("shape");
1650 assert!(
1651 message
1652 .starts_with("list item 2, item 1: boolean input is only valid for boolean data"),
1653 "{message}"
1654 );
1655 }
1656
1657 #[test]
1658 fn nested_literal_under_list_type_is_shape_error() {
1659 let nested = LemmaType::list_of(Arc::new(mass_list_type()));
1660 let message = evaluate_data_shape_error(
1661 &EvaluateDataValue::List(vec![EvaluateDataValue::Literal("1 kilogram".to_string())]),
1662 &nested,
1663 )
1664 .expect("shape");
1665 assert!(
1666 message.starts_with("list item 1: literal overlay"),
1667 "{message}"
1668 );
1669 }
1670
1671 #[test]
1672 fn valid_nested_measure_list_has_no_shape_error() {
1673 let message = evaluate_data_shape_error(
1674 &EvaluateDataValue::List(vec![
1675 unit_map("kilogram", "2"),
1676 EvaluateDataValue::Literal("500 gram".to_string()),
1677 ]),
1678 &mass_list_type(),
1679 );
1680 assert!(message.is_none(), "{message:?}");
1681 }
1682
1683 fn load_engine(code: &str) -> crate::Engine {
1684 let mut engine = crate::Engine::new();
1685 engine
1686 .load([(crate::SourceType::Volatile, code.to_string())])
1687 .expect("load");
1688 engine
1689 }
1690
1691 fn ordered(pairs: &[(&str, EvaluateDataValue)]) -> IndexMap<String, EvaluateDataValue> {
1692 pairs
1693 .iter()
1694 .map(|(k, v)| (k.to_string(), v.clone()))
1695 .collect()
1696 }
1697
1698 fn sample_shallow() -> EvaluateDataValue {
1699 EvaluateDataValue::Map(ordered(&[
1700 (
1701 "price",
1702 EvaluateDataValue::Map(ordered(&[("eur", EvaluateDataValue::from("84"))])),
1703 ),
1704 (
1705 "fares",
1706 EvaluateDataValue::List(vec![
1707 EvaluateDataValue::from("89 eur"),
1708 EvaluateDataValue::Boolean(true),
1709 EvaluateDataValue::Map(IndexMap::new()),
1710 EvaluateDataValue::List(Vec::new()),
1711 ]),
1712 ),
1713 ]))
1714 }
1715
1716 #[test]
1717 fn debug_matches_derived_layout_compact_and_pretty() {
1718 let value = sample_shallow();
1719 assert_eq!(
1720 format!("{value:?}"),
1721 r#"Map({"price": Map({"eur": Literal("84")}), "fares": List([Literal("89 eur"), Boolean(true), Map({}), List([])])})"#
1722 );
1723 assert_eq!(
1724 format!("{value:#?}"),
1725 concat!(
1726 "Map(\n",
1727 " {\n",
1728 " \"price\": Map(\n",
1729 " {\n",
1730 " \"eur\": Literal(\n",
1731 " \"84\",\n",
1732 " ),\n",
1733 " },\n",
1734 " ),\n",
1735 " \"fares\": List(\n",
1736 " [\n",
1737 " Literal(\n",
1738 " \"89 eur\",\n",
1739 " ),\n",
1740 " Boolean(\n",
1741 " true,\n",
1742 " ),\n",
1743 " Map(\n",
1744 " {},\n",
1745 " ),\n",
1746 " List(\n",
1747 " [],\n",
1748 " ),\n",
1749 " ],\n",
1750 " ),\n",
1751 " },\n",
1752 ")",
1753 )
1754 );
1755 }
1756
1757 #[test]
1758 fn deep_chains_clone_eq_debug_and_drop_on_small_stack() {
1759 const DEPTH: usize = 1_000_000;
1760 std::thread::Builder::new()
1761 .stack_size(256 * 1024)
1762 .spawn(|| {
1763 let mut list = EvaluateDataValue::Literal("leaf".to_string());
1764 for _ in 0..DEPTH {
1765 list = EvaluateDataValue::List(vec![list]);
1766 }
1767 let cloned = list.clone();
1768 assert!(list == cloned);
1769 let rendered = format!("{list:?}");
1770 assert_eq!(
1771 rendered.len(),
1772 DEPTH * "List([".len() + "Literal(\"leaf\")".len() + DEPTH * "])".len()
1773 );
1774 drop(cloned);
1775
1776 let mut map = EvaluateDataValue::Boolean(false);
1777 for _ in 0..DEPTH {
1778 let mut level = IndexMap::new();
1779 level.insert("n".to_string(), map);
1780 map = EvaluateDataValue::Map(level);
1781 }
1782 let cloned = map.clone();
1783 assert!(map == cloned);
1784 assert!(map != list);
1785 drop(cloned);
1786 drop(map);
1787 drop(list);
1788 })
1789 .expect("spawn small-stack thread")
1790 .join()
1791 .expect("deep run data must not overflow the stack");
1792 }
1793
1794 #[test]
1795 fn eq_is_order_insensitive_for_maps_and_ordered_for_lists() {
1796 let forward = EvaluateDataValue::Map(ordered(&[
1797 ("a", EvaluateDataValue::from("1")),
1798 ("b", EvaluateDataValue::from("2")),
1799 ]));
1800 let reversed = EvaluateDataValue::Map(ordered(&[
1801 ("b", EvaluateDataValue::from("2")),
1802 ("a", EvaluateDataValue::from("1")),
1803 ]));
1804 assert!(forward == reversed);
1805 assert!(
1806 EvaluateDataValue::List(vec![
1807 EvaluateDataValue::from("1"),
1808 EvaluateDataValue::from("2")
1809 ]) != EvaluateDataValue::List(vec![
1810 EvaluateDataValue::from("2"),
1811 EvaluateDataValue::from("1")
1812 ])
1813 );
1814 assert!(EvaluateDataValue::from("1") != EvaluateDataValue::Boolean(true));
1815 }
1816
1817 #[test]
1818 fn request_error_names_first_key_in_caller_order() {
1819 let engine = load_engine(
1820 r#"
1821spec inner
1822data n: 1
1823
1824spec s
1825uses left: inner
1826uses right: inner
1827rule out: 1
1828"#,
1829 );
1830 let now = crate::DateTimeValue::now();
1831 let overlay = EvaluateDataValue::Map(ordered(&[("n", EvaluateDataValue::from("2"))]));
1832 let left_first = engine
1833 .evaluate(
1834 None,
1835 "s",
1836 Some(&now),
1837 ordered(&[("left", overlay.clone()), ("right", overlay.clone())]),
1838 None,
1839 false,
1840 )
1841 .expect_err("uses alias map is a request error");
1842 assert!(left_first.to_string().contains("'left'"), "{left_first}");
1843 assert!(!left_first.to_string().contains("'right'"), "{left_first}");
1844
1845 let right_first = engine
1846 .evaluate(
1847 None,
1848 "s",
1849 Some(&now),
1850 ordered(&[("right", overlay.clone()), ("left", overlay)]),
1851 None,
1852 false,
1853 )
1854 .expect_err("uses alias map is a request error");
1855 assert!(right_first.to_string().contains("'right'"), "{right_first}");
1856 assert!(!right_first.to_string().contains("'left'"), "{right_first}");
1857 }
1858
1859 fn roster_engine() -> crate::Engine {
1860 load_engine(
1861 r#"
1862spec person
1863data age: number
1864 -> minimum 0
1865data score: number
1866 -> minimum 0
1867
1868spec roster
1869uses people: person list
1870rule n: people -> count
1871"#,
1872 )
1873 }
1874
1875 #[test]
1876 fn row_veto_names_first_field_in_caller_order() {
1877 let engine = roster_engine();
1878 let now = crate::DateTimeValue::now();
1879 let age_first = engine
1880 .evaluate(
1881 None,
1882 "roster",
1883 Some(&now),
1884 ordered(&[(
1885 "people",
1886 EvaluateDataValue::List(vec![EvaluateDataValue::Map(ordered(&[
1887 ("age", EvaluateDataValue::from("-1")),
1888 ("score", EvaluateDataValue::from("-1")),
1889 ]))]),
1890 )]),
1891 Some(&["n".to_string()]),
1892 false,
1893 )
1894 .expect("evaluate");
1895 let reason = age_first
1896 .results
1897 .get("n")
1898 .expect("n")
1899 .veto_reason
1900 .as_deref()
1901 .expect("veto");
1902 assert!(reason.contains("age"), "{reason}");
1903 assert!(!reason.contains("score"), "{reason}");
1904
1905 let score_first = engine
1906 .evaluate(
1907 None,
1908 "roster",
1909 Some(&now),
1910 ordered(&[(
1911 "people",
1912 EvaluateDataValue::List(vec![EvaluateDataValue::Map(ordered(&[
1913 ("score", EvaluateDataValue::from("-1")),
1914 ("age", EvaluateDataValue::from("-1")),
1915 ]))]),
1916 )]),
1917 Some(&["n".to_string()]),
1918 false,
1919 )
1920 .expect("evaluate");
1921 let reason = score_first
1922 .results
1923 .get("n")
1924 .expect("n")
1925 .veto_reason
1926 .as_deref()
1927 .expect("veto");
1928 assert!(reason.contains("score"), "{reason}");
1929 assert!(!reason.contains("age"), "{reason}");
1930 }
1931
1932 #[test]
1933 fn row_unknown_fields_are_ignored() {
1934 let engine = roster_engine();
1935 let now = crate::DateTimeValue::now();
1936 let bogus_first = engine
1937 .evaluate(
1938 None,
1939 "roster",
1940 Some(&now),
1941 ordered(&[(
1942 "people",
1943 EvaluateDataValue::List(vec![EvaluateDataValue::Map(ordered(&[
1944 ("bogus", EvaluateDataValue::from("1")),
1945 ("also", EvaluateDataValue::from("2")),
1946 ("age", EvaluateDataValue::from("1")),
1947 ("score", EvaluateDataValue::from("1")),
1948 ]))]),
1949 )]),
1950 None,
1951 false,
1952 )
1953 .expect("unknown row fields are ignored");
1954 assert!(bogus_first.results.get("n").is_some());
1955
1956 let also_first = engine
1957 .evaluate(
1958 None,
1959 "roster",
1960 Some(&now),
1961 ordered(&[(
1962 "people",
1963 EvaluateDataValue::List(vec![EvaluateDataValue::Map(ordered(&[
1964 ("also", EvaluateDataValue::from("2")),
1965 ("bogus", EvaluateDataValue::from("1")),
1966 ("age", EvaluateDataValue::from("1")),
1967 ("score", EvaluateDataValue::from("1")),
1968 ]))]),
1969 )]),
1970 None,
1971 false,
1972 )
1973 .expect("unknown row fields are ignored");
1974 assert!(also_first.results.get("n").is_some());
1975 }
1976
1977 fn tags_engine(limits: crate::ResourceLimits) -> crate::Engine {
1978 let mut engine = crate::Engine::with_limits(limits);
1979 engine
1980 .load([(
1981 crate::SourceType::Volatile,
1982 "spec tagged\ndata tags: text list\nrule n: tags -> count".to_string(),
1983 )])
1984 .expect("load");
1985 engine
1986 }
1987
1988 fn evaluate_tags(engine: &crate::Engine, tags: Vec<String>) -> crate::Response {
1989 let now = crate::DateTimeValue::now();
1990 engine
1991 .evaluate(
1992 None,
1993 "tagged",
1994 Some(&now),
1995 ordered(&[(
1996 "tags",
1997 EvaluateDataValue::List(
1998 tags.into_iter().map(EvaluateDataValue::Literal).collect(),
1999 ),
2000 )]),
2001 Some(&["n".to_string()]),
2002 false,
2003 )
2004 .expect("evaluate")
2005 }
2006
2007 #[test]
2008 fn value_list_over_value_byte_limit_binds() {
2009 let engine = tags_engine(crate::ResourceLimits::default());
2010 let limit = crate::ResourceLimits::default().max_data_value_bytes;
2011 let tags: Vec<String> = (0..200).map(|i| format!("customer-tag-{i:04}")).collect();
2012 let total: usize = tags.iter().map(String::len).sum();
2013 assert!(
2014 total > limit,
2015 "fixture must exceed {limit} bytes, has {total}"
2016 );
2017
2018 let response = evaluate_tags(&engine, tags);
2019 let result = response.results.get("n").expect("n");
2020 assert_eq!(result.veto_reason, None);
2021 assert_eq!(result.result(), Some("200"));
2022 }
2023
2024 #[test]
2025 fn value_list_item_over_value_byte_limit_vetoes() {
2026 let engine = tags_engine(crate::ResourceLimits {
2027 max_data_value_bytes: 50,
2028 ..crate::ResourceLimits::default()
2029 });
2030 let response = evaluate_tags(
2031 &engine,
2032 vec!["vip".to_string(), "x".repeat(51), "trial".to_string()],
2033 );
2034 let reason = response
2035 .results
2036 .get("n")
2037 .expect("n")
2038 .veto_reason
2039 .as_deref()
2040 .expect("veto");
2041 assert!(
2042 reason.contains("list item 2 exceeds the size limit"),
2043 "{reason}"
2044 );
2045 }
2046}