sim_shape/options.rs
1//! Shape-backed validation for option maps.
2//!
3//! Option surfaces often start as runtime key/value pairs and then fan out into
4//! local typed readers. This module provides the shared structural check for the
5//! map shape before a consumer performs domain-specific parsing.
6
7use std::sync::Arc;
8
9use sim_kernel::{Cx, Expr, Result, Symbol};
10
11use crate::{Shape, ShapeMatch, TableExtraPolicy, TableFieldSpec, TableShape};
12
13/// One option field constraint for [`check_option_map`].
14pub struct OptionFieldSpec {
15 /// Symbol key to look up in the option map.
16 pub key: Symbol,
17 /// Shape that must accept the option value when present.
18 pub shape: Arc<dyn Shape>,
19 /// Whether this option must be present.
20 pub required: bool,
21}
22
23impl OptionFieldSpec {
24 /// Build a required option field.
25 pub fn required(key: Symbol, shape: Arc<dyn Shape>) -> Self {
26 Self {
27 key,
28 shape,
29 required: true,
30 }
31 }
32
33 /// Build an optional option field.
34 pub fn optional(key: Symbol, shape: Arc<dyn Shape>) -> Self {
35 Self {
36 key,
37 shape,
38 required: false,
39 }
40 }
41}
42
43/// Check an option-map expression against field specs and an extra-key policy.
44///
45/// This is intentionally a thin wrapper around [`TableShape`]: it gives option
46/// parsers a domain-named entry point while preserving the same match scoring,
47/// captures, and diagnostics as the table grammar.
48pub fn check_option_map(
49 cx: &mut Cx,
50 expr: &Expr,
51 fields: Vec<OptionFieldSpec>,
52 extra: TableExtraPolicy,
53) -> Result<ShapeMatch> {
54 let fields = fields
55 .into_iter()
56 .map(|field| TableFieldSpec {
57 key: field.key,
58 shape: field.shape,
59 required: field.required,
60 })
61 .collect();
62 TableShape::new(fields, extra).check_expr(cx, expr)
63}