1use std::collections::BTreeMap;
4use std::sync::Arc;
5
6use num_bigint::BigInt;
7use num_traits::ToPrimitive;
8use serde::{Deserialize, Serialize};
9
10use crate::context::{Conversion, ExecContext};
11use crate::envelope::Exactness;
12use crate::error::{EngineError, ErrorCode};
13use crate::number::{Decimal, Number, NumericMode};
14use crate::schema::ValueSchema;
15use crate::value::Value;
16
17#[derive(Clone)]
19pub struct Module {
20 pub descriptor: ModuleDescriptor,
21 pub functions: Vec<Arc<dyn Function>>,
22}
23
24impl Module {
25 pub fn new(descriptor: ModuleDescriptor, functions: Vec<Arc<dyn Function>>) -> Module {
26 Module {
27 descriptor,
28 functions,
29 }
30 }
31}
32
33#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
35pub struct ModuleDescriptor {
36 pub id: String,
37 pub title: String,
38 pub version: String,
39 pub description: String,
40 pub capabilities: Vec<String>,
41 pub dependencies: Vec<String>,
42 pub supported_modes: Vec<NumericMode>,
43 pub license: String,
44 pub source: String,
45}
46
47impl ModuleDescriptor {
48 pub fn new(
49 id: impl Into<String>,
50 title: impl Into<String>,
51 version: impl Into<String>,
52 description: impl Into<String>,
53 ) -> ModuleDescriptor {
54 ModuleDescriptor {
55 id: id.into(),
56 title: title.into(),
57 version: version.into(),
58 description: description.into(),
59 capabilities: Vec::new(),
60 dependencies: Vec::new(),
61 supported_modes: vec![NumericMode::Auto],
62 license: "MIT".to_string(),
63 source: String::new(),
64 }
65 }
66
67 pub fn with_capabilities(
68 mut self,
69 capabilities: impl IntoIterator<Item = impl Into<String>>,
70 ) -> Self {
71 self.capabilities = capabilities.into_iter().map(Into::into).collect();
72 self
73 }
74
75 pub fn with_dependencies(
76 mut self,
77 dependencies: impl IntoIterator<Item = impl Into<String>>,
78 ) -> Self {
79 self.dependencies = dependencies.into_iter().map(Into::into).collect();
80 self
81 }
82
83 pub fn with_modes(mut self, modes: impl IntoIterator<Item = NumericMode>) -> Self {
84 self.supported_modes = modes.into_iter().collect();
85 self
86 }
87
88 pub fn with_source(mut self, source: impl Into<String>) -> Self {
89 self.source = source.into();
90 self
91 }
92}
93
94#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
96#[serde(rename_all = "snake_case")]
97pub enum Purity {
98 Pure,
99}
100
101#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
103#[serde(rename_all = "snake_case")]
104pub enum Determinism {
105 Deterministic,
106 SeededRandom,
107}
108
109#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
111#[serde(rename_all = "snake_case")]
112pub enum CostClass {
113 Constant,
114 Linear,
115 Quadratic,
116 Cubic,
117 Iterative,
118}
119
120#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
122pub struct Deprecation {
123 pub since: String,
124 pub message: String,
125 #[serde(skip_serializing_if = "Option::is_none")]
126 pub replacement: Option<String>,
127}
128
129#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
131pub struct ParamDescriptor {
132 pub name: String,
133 pub description: String,
134 pub schema: ValueSchema,
135 pub required: bool,
136 pub numeric_shorthand: bool,
138 pub positional: bool,
140}
141
142impl ParamDescriptor {
143 pub fn required(
144 name: impl Into<String>,
145 description: impl Into<String>,
146 schema: ValueSchema,
147 ) -> ParamDescriptor {
148 let numeric = schema.is_numeric();
149 ParamDescriptor {
150 name: name.into(),
151 description: description.into(),
152 schema,
153 required: true,
154 numeric_shorthand: numeric,
155 positional: true,
156 }
157 }
158
159 pub fn optional(
160 name: impl Into<String>,
161 description: impl Into<String>,
162 schema: ValueSchema,
163 ) -> ParamDescriptor {
164 let numeric = schema.is_numeric();
165 ParamDescriptor {
166 name: name.into(),
167 description: description.into(),
168 schema,
169 required: false,
170 numeric_shorthand: numeric,
171 positional: true,
172 }
173 }
174
175 pub fn with_shorthand(mut self, allowed: bool) -> Self {
176 self.numeric_shorthand = allowed;
177 self
178 }
179
180 pub fn with_positional(mut self, allowed: bool) -> Self {
181 self.positional = allowed;
182 self
183 }
184}
185
186#[derive(Clone, Debug, PartialEq)]
191pub enum ExampleExpectation {
192 Value(Value),
193 Error(ErrorCode),
194 Contains(String),
195}
196
197impl Serialize for ExampleExpectation {
198 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
199 use serde::ser::SerializeMap;
200 match self {
201 ExampleExpectation::Value(value) => {
202 let mut map = serializer.serialize_map(Some(2))?;
203 map.serialize_entry("type", "value")?;
204 map.serialize_entry("value", value)?;
205 map.end()
206 }
207 ExampleExpectation::Error(code) => {
208 let mut map = serializer.serialize_map(Some(2))?;
209 map.serialize_entry("type", "error")?;
210 map.serialize_entry("code", code)?;
211 map.end()
212 }
213 ExampleExpectation::Contains(text) => {
214 let mut map = serializer.serialize_map(Some(2))?;
215 map.serialize_entry("type", "contains")?;
216 map.serialize_entry("text", text)?;
217 map.end()
218 }
219 }
220 }
221}
222
223impl<'de> Deserialize<'de> for ExampleExpectation {
224 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
225 use serde::de::Error as DeError;
226 let raw = serde_json::Value::deserialize(deserializer)?;
227 let kind = raw
228 .get("type")
229 .and_then(|value| value.as_str())
230 .ok_or_else(|| D::Error::custom("example expectation is missing \"type\""))?;
231 match kind {
232 "value" => {
233 let value = raw
234 .get("value")
235 .ok_or_else(|| D::Error::custom("value expectation is missing \"value\""))?;
236 Ok(ExampleExpectation::Value(
237 serde_json::from_value(value.clone()).map_err(D::Error::custom)?,
238 ))
239 }
240 "error" => {
241 let code = raw
242 .get("code")
243 .ok_or_else(|| D::Error::custom("error expectation is missing \"code\""))?;
244 Ok(ExampleExpectation::Error(
245 serde_json::from_value(code.clone()).map_err(D::Error::custom)?,
246 ))
247 }
248 "contains" => {
249 let text = raw
250 .get("text")
251 .and_then(|value| value.as_str())
252 .ok_or_else(|| D::Error::custom("contains expectation is missing \"text\""))?;
253 Ok(ExampleExpectation::Contains(text.to_string()))
254 }
255 other => Err(D::Error::custom(format!(
256 "unknown example expectation type {other:?}"
257 ))),
258 }
259 }
260}
261
262#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
265pub struct Example {
266 pub title: String,
267 pub arguments: BTreeMap<String, Value>,
268 #[serde(skip_serializing_if = "Option::is_none")]
269 pub expected: Option<ExampleExpectation>,
270 #[serde(skip_serializing_if = "Option::is_none")]
271 pub note: Option<String>,
272}
273
274impl Example {
275 pub fn new(title: impl Into<String>, arguments: BTreeMap<String, Value>) -> Example {
276 Example {
277 title: title.into(),
278 arguments,
279 expected: None,
280 note: None,
281 }
282 }
283
284 pub fn with_value(mut self, value: Value) -> Example {
285 self.expected = Some(ExampleExpectation::Value(value));
286 self
287 }
288
289 pub fn with_error(mut self, code: ErrorCode) -> Example {
290 self.expected = Some(ExampleExpectation::Error(code));
291 self
292 }
293
294 pub fn with_contains(mut self, text: impl Into<String>) -> Example {
295 self.expected = Some(ExampleExpectation::Contains(text.into()));
296 self
297 }
298}
299
300#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
302pub struct FunctionDescriptor {
303 pub id: String,
304 pub module: String,
305 pub version: String,
306 pub title: String,
307 pub summary: String,
308 pub description: String,
309 pub parameters: Vec<ParamDescriptor>,
310 pub output: ValueSchema,
311 pub output_description: String,
312 pub modes: Vec<NumericMode>,
313 pub purity: Purity,
314 pub determinism: Determinism,
315 pub cost: CostClass,
316 pub units_rule: String,
317 pub method_ref: String,
318 pub examples: Vec<Example>,
319 #[serde(skip_serializing_if = "Option::is_none")]
320 pub deprecated: Option<Deprecation>,
321 pub tags: Vec<String>,
322}
323
324impl FunctionDescriptor {
325 pub fn new(
326 id: impl Into<String>,
327 module: impl Into<String>,
328 version: impl Into<String>,
329 title: impl Into<String>,
330 summary: impl Into<String>,
331 ) -> FunctionDescriptor {
332 FunctionDescriptor {
333 id: id.into(),
334 module: module.into(),
335 version: version.into(),
336 title: title.into(),
337 summary: summary.into(),
338 description: String::new(),
339 parameters: Vec::new(),
340 output: ValueSchema::Any,
341 output_description: String::new(),
342 modes: vec![NumericMode::Auto],
343 purity: Purity::Pure,
344 determinism: Determinism::Deterministic,
345 cost: CostClass::Constant,
346 units_rule: String::new(),
347 method_ref: String::new(),
348 examples: Vec::new(),
349 deprecated: None,
350 tags: Vec::new(),
351 }
352 }
353
354 pub fn with_description(mut self, description: impl Into<String>) -> Self {
355 self.description = description.into();
356 self
357 }
358
359 pub fn with_parameters(mut self, parameters: Vec<ParamDescriptor>) -> Self {
360 self.parameters = parameters;
361 self
362 }
363
364 pub fn with_output(mut self, output: ValueSchema, description: impl Into<String>) -> Self {
365 self.output = output;
366 self.output_description = description.into();
367 self
368 }
369
370 pub fn with_modes(mut self, modes: impl IntoIterator<Item = NumericMode>) -> Self {
371 self.modes = modes.into_iter().collect();
372 self
373 }
374
375 pub fn with_cost(mut self, cost: CostClass) -> Self {
376 self.cost = cost;
377 self
378 }
379
380 pub fn with_units_rule(mut self, rule: impl Into<String>) -> Self {
381 self.units_rule = rule.into();
382 self
383 }
384
385 pub fn with_method_ref(mut self, method_ref: impl Into<String>) -> Self {
386 self.method_ref = method_ref.into();
387 self
388 }
389
390 pub fn with_examples(mut self, examples: Vec<Example>) -> Self {
391 self.examples = examples;
392 self
393 }
394
395 pub fn with_tags(mut self, tags: impl IntoIterator<Item = impl Into<String>>) -> Self {
396 self.tags = tags.into_iter().map(Into::into).collect();
397 self
398 }
399
400 pub fn with_deprecation(mut self, deprecation: Deprecation) -> Self {
401 self.deprecated = Some(deprecation);
402 self
403 }
404
405 pub fn parameter(&self, name: &str) -> Option<&ParamDescriptor> {
406 self.parameters.iter().find(|p| p.name == name)
407 }
408}
409
410#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
412pub struct FunctionRef {
413 pub id: String,
414 pub version: String,
415}
416
417#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
419pub struct Warning {
420 pub code: String,
421 pub message: String,
422 #[serde(skip_serializing_if = "Option::is_none")]
423 pub details: Option<Value>,
424}
425
426impl Warning {
427 pub fn new(code: impl Into<String>, message: impl Into<String>) -> Warning {
428 Warning {
429 code: code.into(),
430 message: message.into(),
431 details: None,
432 }
433 }
434
435 pub fn with_details(mut self, details: Value) -> Warning {
436 self.details = Some(details);
437 self
438 }
439}
440
441#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
443#[serde(rename_all = "snake_case")]
444pub enum AssumptionSource {
445 UserSupplied,
447 Checked,
449 Unverified,
451}
452
453#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
455pub struct Assumption {
456 pub id: String,
457 pub statement: String,
458 pub source: AssumptionSource,
459}
460
461impl Assumption {
462 pub fn user_supplied(id: impl Into<String>, statement: impl Into<String>) -> Assumption {
463 Assumption {
464 id: id.into(),
465 statement: statement.into(),
466 source: AssumptionSource::UserSupplied,
467 }
468 }
469
470 pub fn checked(id: impl Into<String>, statement: impl Into<String>) -> Assumption {
471 Assumption {
472 id: id.into(),
473 statement: statement.into(),
474 source: AssumptionSource::Checked,
475 }
476 }
477
478 pub fn unverified(id: impl Into<String>, statement: impl Into<String>) -> Assumption {
479 Assumption {
480 id: id.into(),
481 statement: statement.into(),
482 source: AssumptionSource::Unverified,
483 }
484 }
485}
486
487#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
490pub struct ErrorEstimate {
491 pub kind: String,
492 pub bound: Value,
493 pub method: String,
494 #[serde(skip_serializing_if = "Option::is_none")]
495 pub notes: Option<String>,
496}
497
498impl ErrorEstimate {
499 pub fn new(kind: impl Into<String>, bound: Value, method: impl Into<String>) -> ErrorEstimate {
500 ErrorEstimate {
501 kind: kind.into(),
502 bound,
503 method: method.into(),
504 notes: None,
505 }
506 }
507
508 pub fn with_notes(mut self, notes: impl Into<String>) -> ErrorEstimate {
509 self.notes = Some(notes.into());
510 self
511 }
512}
513
514#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
516pub struct TraceStep {
517 pub op: String,
518 pub detail: BTreeMap<String, Value>,
519}
520
521#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
524pub struct Trace {
525 pub steps: Vec<TraceStep>,
526}
527
528impl Trace {
529 pub fn new() -> Trace {
530 Trace { steps: Vec::new() }
531 }
532
533 pub fn push(&mut self, step: TraceStep) {
534 self.steps.push(step);
535 }
536}
537
538#[derive(Clone, Debug)]
540pub struct Outcome {
541 pub value: Value,
542 pub exactness: Exactness,
543 pub warnings: Vec<Warning>,
544 pub assumptions: Vec<Assumption>,
545 pub error_estimate: Option<ErrorEstimate>,
546 pub trace: Option<Trace>,
547 pub conversions: Vec<Conversion>,
548}
549
550impl Outcome {
551 pub fn new(value: Value, exactness: Exactness) -> Outcome {
552 Outcome {
553 value,
554 exactness,
555 warnings: Vec::new(),
556 assumptions: Vec::new(),
557 error_estimate: None,
558 trace: None,
559 conversions: Vec::new(),
560 }
561 }
562
563 pub fn exact(value: Value) -> Outcome {
564 Outcome::new(value, Exactness::Exact)
565 }
566
567 pub fn rounded(value: Value) -> Outcome {
568 Outcome::new(value, Exactness::Rounded)
569 }
570
571 pub fn approximate(value: Value) -> Outcome {
572 Outcome::new(value, Exactness::Approximate)
573 }
574
575 pub fn with_warning(mut self, warning: Warning) -> Outcome {
576 self.warnings.push(warning);
577 self
578 }
579
580 pub fn with_assumption(mut self, assumption: Assumption) -> Outcome {
581 self.assumptions.push(assumption);
582 self
583 }
584
585 pub fn with_error_estimate(mut self, estimate: ErrorEstimate) -> Outcome {
586 self.error_estimate = Some(estimate);
587 self
588 }
589
590 pub fn with_trace(mut self, trace: Trace) -> Outcome {
591 self.trace = Some(trace);
592 self
593 }
594
595 pub fn with_conversion(mut self, conversion: Conversion) -> Outcome {
596 self.conversions.push(conversion);
597 self
598 }
599}
600
601#[derive(Clone, Debug, Default)]
603pub struct Args {
604 values: BTreeMap<String, Value>,
605}
606
607impl Args {
608 pub fn new(values: BTreeMap<String, Value>) -> Args {
609 Args { values }
610 }
611
612 pub fn get(&self, name: &str) -> Option<&Value> {
613 self.values.get(name)
614 }
615
616 pub fn contains(&self, name: &str) -> bool {
617 self.values.contains_key(name)
618 }
619
620 pub fn iter(&self) -> impl Iterator<Item = (&String, &Value)> {
621 self.values.iter()
622 }
623
624 pub fn into_map(self) -> BTreeMap<String, Value> {
625 self.values
626 }
627
628 fn expected(name: &str, kind: &str) -> EngineError {
629 EngineError::malformed(format!(
630 "missing or invalid argument {name:?}: expected {kind}"
631 ))
632 .with_path(name.to_string())
633 }
634
635 pub fn require(&self, name: &str) -> Result<&Value, EngineError> {
636 self.values
637 .get(name)
638 .ok_or_else(|| Self::expected(name, "a value"))
639 }
640
641 pub fn number(&self, name: &str) -> Result<&Number, EngineError> {
642 self.require(name)?
643 .as_number()
644 .map_err(|e| e.with_path(name.to_string()))
645 }
646
647 pub fn optional_number(&self, name: &str) -> Result<Option<&Number>, EngineError> {
648 match self.values.get(name) {
649 None | Some(Value::Null) => Ok(None),
650 Some(value) => Ok(Some(
651 value
652 .as_number()
653 .map_err(|e| e.with_path(name.to_string()))?,
654 )),
655 }
656 }
657
658 pub fn text(&self, name: &str) -> Result<&str, EngineError> {
659 self.require(name)?
660 .as_text()
661 .map_err(|e| e.with_path(name.to_string()))
662 }
663
664 pub fn optional_text(&self, name: &str) -> Result<Option<&str>, EngineError> {
665 match self.values.get(name) {
666 None | Some(Value::Null) => Ok(None),
667 Some(value) => Ok(Some(
668 value.as_text().map_err(|e| e.with_path(name.to_string()))?,
669 )),
670 }
671 }
672
673 pub fn bool(&self, name: &str) -> Result<bool, EngineError> {
674 self.require(name)?
675 .as_bool()
676 .map_err(|e| e.with_path(name.to_string()))
677 }
678
679 pub fn optional_bool(&self, name: &str) -> Result<Option<bool>, EngineError> {
680 match self.values.get(name) {
681 None | Some(Value::Null) => Ok(None),
682 Some(value) => Ok(Some(
683 value.as_bool().map_err(|e| e.with_path(name.to_string()))?,
684 )),
685 }
686 }
687
688 pub fn array(&self, name: &str) -> Result<&[Value], EngineError> {
689 self.require(name)?
690 .as_array()
691 .map_err(|e| e.with_path(name.to_string()))
692 }
693
694 pub fn record(&self, name: &str) -> Result<&BTreeMap<String, Value>, EngineError> {
695 self.require(name)?
696 .as_record()
697 .map_err(|e| e.with_path(name.to_string()))
698 }
699
700 pub fn money(&self, name: &str) -> Result<(&Number, &str), EngineError> {
701 self.require(name)?
702 .as_money()
703 .map_err(|e| e.with_path(name.to_string()))
704 }
705
706 pub fn quantity(&self, name: &str) -> Result<(&Number, crate::value::Dimension), EngineError> {
707 self.require(name)?
708 .as_quantity()
709 .map_err(|e| e.with_path(name.to_string()))
710 }
711
712 pub fn integer(&self, name: &str) -> Result<BigInt, EngineError> {
713 match self.number(name)? {
714 Number::Integer(value) => Ok(value.clone()),
715 other => Err(Self::expected(
716 name,
717 &format!("an integer, found {}", other.kind_name()),
718 )),
719 }
720 }
721
722 pub fn optional_integer(&self, name: &str) -> Result<Option<BigInt>, EngineError> {
723 match self.optional_number(name)? {
724 None => Ok(None),
725 Some(Number::Integer(value)) => Ok(Some(value.clone())),
726 Some(other) => Err(Self::expected(
727 name,
728 &format!("an integer, found {}", other.kind_name()),
729 )),
730 }
731 }
732
733 pub fn usize_param(&self, name: &str) -> Result<usize, EngineError> {
735 let value = self.integer(name)?;
736 value
737 .to_usize()
738 .ok_or_else(|| Self::expected(name, "a non-negative machine-sized integer"))
739 }
740
741 pub fn u32_param(&self, name: &str) -> Result<u32, EngineError> {
742 let value = self.integer(name)?;
743 value
744 .to_u32()
745 .ok_or_else(|| Self::expected(name, "a non-negative 32-bit integer"))
746 }
747
748 pub fn u64_param(&self, name: &str) -> Result<u64, EngineError> {
749 let value = self.integer(name)?;
750 value
751 .to_u64()
752 .ok_or_else(|| Self::expected(name, "a non-negative 64-bit integer"))
753 }
754
755 pub fn f64_param(&self, name: &str) -> Result<f64, EngineError> {
758 let value = self.number(name)?;
759 value
760 .to_f64()
761 .ok_or_else(|| Self::expected(name, "a value representable as float64"))
762 }
763
764 pub fn optional_f64(&self, name: &str) -> Result<Option<f64>, EngineError> {
765 match self.optional_number(name)? {
766 None => Ok(None),
767 Some(value) => value
768 .to_f64()
769 .map(Some)
770 .ok_or_else(|| Self::expected(name, "a value representable as float64")),
771 }
772 }
773
774 pub fn decimal(&self, name: &str) -> Result<Decimal, EngineError> {
775 match self.number(name)? {
776 Number::Decimal(value) => Ok(value.clone()),
777 Number::Integer(value) => Ok(Decimal::from_bigint(value.clone())),
778 other => Err(Self::expected(
779 name,
780 &format!("a decimal, found {}", other.kind_name()),
781 )),
782 }
783 }
784
785 pub fn optional_decimal(&self, name: &str) -> Result<Option<Decimal>, EngineError> {
786 match self.optional_number(name)? {
787 None => Ok(None),
788 Some(number) => match number {
789 Number::Decimal(value) => Ok(Some(value.clone())),
790 Number::Integer(value) => Ok(Some(Decimal::from_bigint(value.clone()))),
791 other => Err(Self::expected(
792 name,
793 &format!("a decimal, found {}", other.kind_name()),
794 )),
795 },
796 }
797 }
798}
799
800pub trait Function: Send + Sync {
802 fn descriptor(&self) -> &FunctionDescriptor;
803 fn invoke(&self, args: &Args, ctx: &ExecContext) -> Result<Outcome, EngineError>;
804}
805
806pub struct SimpleFunction {
813 descriptor: FunctionDescriptor,
814 invoke: fn(&Args, &ExecContext) -> Result<Outcome, EngineError>,
815}
816
817impl SimpleFunction {
818 pub fn new(
819 descriptor: FunctionDescriptor,
820 invoke: fn(&Args, &ExecContext) -> Result<Outcome, EngineError>,
821 ) -> SimpleFunction {
822 SimpleFunction { descriptor, invoke }
823 }
824
825 pub fn arc(
826 descriptor: FunctionDescriptor,
827 invoke: fn(&Args, &ExecContext) -> Result<Outcome, EngineError>,
828 ) -> Arc<dyn Function> {
829 Arc::new(SimpleFunction::new(descriptor, invoke))
830 }
831}
832
833impl Function for SimpleFunction {
834 fn descriptor(&self) -> &FunctionDescriptor {
835 &self.descriptor
836 }
837
838 fn invoke(&self, args: &Args, ctx: &ExecContext) -> Result<Outcome, EngineError> {
839 (self.invoke)(args, ctx)
840 }
841}
842
843pub fn require_mode(
845 ctx: &ExecContext,
846 modes: &[NumericMode],
847 function_id: &str,
848) -> Result<(), EngineError> {
849 if modes.contains(&ctx.numeric.mode) {
850 Ok(())
851 } else {
852 Err(EngineError::new(
853 ErrorCode::UnsupportedNumericMode,
854 format!(
855 "function {function_id} supports modes {:?}, not {}",
856 modes, ctx.numeric.mode
857 ),
858 ))
859 }
860}