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