Skip to main content

bicmath_core/
envelope.rs

1//! The versioned result envelope shared by every adapter.
2
3use std::collections::BTreeMap;
4
5use serde::{Deserialize, Serialize};
6
7use crate::WIRE_SCHEMA_VERSION;
8use crate::context::EffectiveContext;
9use crate::contract::{Assumption, ErrorEstimate, FunctionRef, Trace, Warning};
10use crate::error::EngineError;
11use crate::fingerprint::Receipt;
12use crate::value::Value;
13
14/// How exact a result is. These classifications are mutually exclusive and are
15/// never inferred from mere repeatability.
16#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
17#[serde(rename_all = "snake_case")]
18pub enum Exactness {
19    /// The result is mathematically exact under the selected representation.
20    Exact,
21    /// The result was rounded under a declared context (e.g. decimal division,
22    /// money quantization). The context and inexactness are reported.
23    Rounded,
24    /// The result is a floating-point approximation. Accuracy is bounded by the
25    /// method, not by the representation alone.
26    Approximate,
27}
28
29impl Exactness {
30    pub fn as_str(self) -> &'static str {
31        match self {
32            Exactness::Exact => "exact",
33            Exactness::Rounded => "rounded",
34            Exactness::Approximate => "approximate",
35        }
36    }
37
38    /// Combine classifications, taking the least exact.
39    pub fn combine(self, other: Exactness) -> Exactness {
40        match (self, other) {
41            (Exactness::Approximate, _) | (_, Exactness::Approximate) => Exactness::Approximate,
42            (Exactness::Rounded, _) | (_, Exactness::Rounded) => Exactness::Rounded,
43            _ => Exactness::Exact,
44        }
45    }
46}
47
48/// Engine build identity. Timestamps and request ids are deliberately excluded
49/// from the fingerprint.
50#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
51pub struct EngineInfo {
52    pub name: String,
53    pub version: String,
54    #[serde(skip_serializing_if = "Option::is_none")]
55    pub build: Option<String>,
56    pub modules: BTreeMap<String, String>,
57}
58
59impl EngineInfo {
60    pub fn current() -> EngineInfo {
61        EngineInfo {
62            name: "bicmath".to_string(),
63            version: crate::VERSION.to_string(),
64            build: option_env!("BICMATH_BUILD_ID").map(|s| s.to_string()),
65            modules: BTreeMap::new(),
66        }
67    }
68}
69
70/// A successful, inspectable result.
71#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
72pub struct ResultEnvelope {
73    pub schema_version: u32,
74    #[serde(skip_serializing_if = "Option::is_none")]
75    pub function: Option<FunctionRef>,
76    pub engine: EngineInfo,
77    pub result: Value,
78    pub exactness: Exactness,
79    pub context: EffectiveContext,
80    #[serde(skip_serializing_if = "Option::is_none")]
81    pub error_estimate: Option<ErrorEstimate>,
82    #[serde(default, skip_serializing_if = "Vec::is_empty")]
83    pub warnings: Vec<Warning>,
84    #[serde(default, skip_serializing_if = "Vec::is_empty")]
85    pub assumptions: Vec<Assumption>,
86    pub fingerprint: String,
87    #[serde(skip_serializing_if = "Option::is_none")]
88    pub receipt: Option<Receipt>,
89    #[serde(skip_serializing_if = "Option::is_none")]
90    pub trace: Option<Trace>,
91}
92
93impl ResultEnvelope {
94    pub fn new(
95        function: Option<FunctionRef>,
96        engine: EngineInfo,
97        result: Value,
98        exactness: Exactness,
99        context: EffectiveContext,
100        fingerprint: String,
101    ) -> ResultEnvelope {
102        ResultEnvelope {
103            schema_version: WIRE_SCHEMA_VERSION,
104            function,
105            engine,
106            result,
107            exactness,
108            context,
109            error_estimate: None,
110            warnings: Vec::new(),
111            assumptions: Vec::new(),
112            fingerprint,
113            receipt: None,
114            trace: None,
115        }
116    }
117}
118
119/// A structured error response. Errors never carry a fabricated result.
120#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
121pub struct ErrorResponse {
122    pub schema_version: u32,
123    pub engine: EngineInfo,
124    pub error: EngineError,
125    #[serde(skip_serializing_if = "Option::is_none")]
126    pub fingerprint: Option<String>,
127}
128
129impl ErrorResponse {
130    pub fn new(engine: EngineInfo, error: EngineError) -> ErrorResponse {
131        ErrorResponse {
132            schema_version: WIRE_SCHEMA_VERSION,
133            engine,
134            error,
135            fingerprint: None,
136        }
137    }
138}