Skip to main content

lemma/evaluation/
evaluate_data.rs

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
20/// Neutral overlay tree. [`EvaluateData::resolve`] reads each value by declared type.
21///
22/// Host input may nest arbitrarily deep, so `Clone`, `Debug`, `PartialEq` and `Drop` are
23/// iterative. Derived versions recurse on the native stack.
24pub enum EvaluateDataValue {
25    /// Unparsed Lemma literal text, parsed against the declared type by [`parse_value_from_string`].
26    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
255/// Tokens of `value` at nesting `level`, matching the derived `Debug` layout.
256fn 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
326/// `Name(field)` with the field's tokens at `field_level`; pretty mode puts it on its own line.
327fn 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
384/// Top-level run-data entries from a host walker. Keys differing only in case: request Error.
385pub 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
394/// Nested object entries from a host walker. Keys differing only in case: request Error.
395pub 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
404/// Host list item that is null (JSON `null`, JS `null` / `undefined`, Java `null`, Elixir `nil`).
405/// A list has no unsupplied row, so this is a request Error, never an omitted item.
406/// `position` is 1-based within its list.
407pub 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
414/// Attribute a walker error to its top-level data key unless already attributed.
415pub 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
502/// Shape mismatches are request Errors from `evaluate_data_shape_error`, checked before parsing.
503/// The mismatch panics below are therefore unreachable.
504fn 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
572/// Read a parsed Lemma literal as a value of `lemma_type`, keeping the written unit.
573pub(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
630/// Resolve a unit the caller actually wrote to a bare declared name.
631///
632/// Qualified paths go through [`UnitIndex::resolve_with_named_types`]. Bare names
633/// accepted only when this data type (or range element) declares that exact name.
634/// Never invents a unit the caller did not pass.
635fn 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
698/// First shape mismatch in `input` against `lemma_type`, at any list depth. `None` if every
699/// value matches the type its parse node will use. List items are reported by 1-based position.
700fn 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/// User-provided data values resolved against a plan's type declarations.
737///
738/// Lightweight and cheap to construct — no plan cloning required. The
739/// [`ExecutionPlan`] stays immutable; callers pass `(&ExecutionPlan, &EvaluateData)`
740/// to evaluation and show paths.
741#[derive(Debug, Clone, Default)]
742pub struct EvaluateData {
743    /// Caller bindings: successful literals or Veto (bad override) per Data.
744    pub bindings: IndexMap<DataPath, OperationResult>,
745    /// Input keys that did not match any plan Data (including Import aliases).
746    pub ignored_unknown: Vec<String>,
747}
748
749impl EvaluateData {
750    /// Parse and validate caller-supplied values against the plan's data declarations.
751    ///
752    /// Unknown keys and Import aliases are ignored (recorded in [`Self::ignored_unknown`]).
753    /// Parse, constraint, options, decimals, and input-oversize failures bind that Data
754    /// as [`OperationResult::Veto`]; evaluation still runs. Duplicate canonical keys Error.
755    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
822/// One level of run data: its entries in caller order. A list entry suspends the level
823/// until its rows are resolved.
824struct 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
1042/// Local plan.data path, or a named-`uses` member leaf matched by `input_key`.
1043fn 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
1053/// Instance list whose rows `key` addresses one field of (`staff.2.salary`).
1054fn 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/// Whether a run-data list value is a top-level one, whose whole row tree is checked
1063/// against the list limits, or rows nested inside one already checked.
1064#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1065enum ListLevel {
1066    Top,
1067    Nested,
1068}
1069
1070/// Rows of one top-level list value across all its levels, `template` being the plan
1071/// its rows read. Over `max_list_nodes` rows in total, or rows nesting deeper than
1072/// `max_list_depth` levels (the spec holding the list is level 1), is a request Error.
1073/// Node count is checked first so a wide tree reports its size, not its depth.
1074fn 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
1120/// Rows of instance list `path` from run data: one object per row, resolved against
1121/// the row spec's template. Unknown fields, a missing key field, or a non-object row
1122/// is a request Error; a vetoed field, a duplicate key or overlapping key ranges veto
1123/// the whole list.
1124/// Frame of one instance list entry: validates the list, then resolves each row through
1125/// a level frame of the row template. A vetoed field, duplicate key or overlapping key
1126/// range resolves the list to a veto.
1127struct 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    /// Adds the resolved row `rows.number`. `Some` is a veto of the whole list.
1265    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}