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