Skip to main content

icydb_diagnostic_code/
fact.rs

1//! Module: fact
2//!
3//! Responsibility: production-safe numeric diagnostic-fact identities.
4//! Does not own: Candid records, rich labels, prose, or subsystem projections.
5//! Boundary: freezes the numeric vocabulary shared by public errors and host tooling.
6
7use std::fmt;
8
9/// Maximum number of numeric facts carried by one public error.
10pub const MAX_PUBLIC_DIAGNOSTIC_FACTS: usize = 80;
11
12macro_rules! define_fact_tag_registry {
13    ($($name:ident = $raw:literal;)+) => {
14        /// Stable semantic identity for one numeric public diagnostic fact.
15        #[derive(Clone, Copy, Eq, Hash, PartialEq)]
16        pub enum DiagnosticFactTag {
17            $(
18                #[doc = concat!("Public fact tag ", stringify!($raw), ".")]
19                $name,
20            )+
21        }
22
23        impl DiagnosticFactTag {
24            /// Return the fixed public wire value.
25            #[must_use]
26            pub const fn raw(self) -> u8 {
27                match self {
28                    $(Self::$name => $raw,)+
29                }
30            }
31
32            /// Recover a known tag from its public wire value.
33            #[must_use]
34            pub const fn known(raw: u8) -> Option<Self> {
35                match raw {
36                    $($raw => Some(Self::$name),)+
37                    _ => None,
38                }
39            }
40        }
41
42        #[cfg(test)]
43        const ORDERED_FACT_TAGS: &[DiagnosticFactTag] = &[
44            $(DiagnosticFactTag::$name,)+
45        ];
46    };
47}
48
49// This table is a public numeric registry. Append only within a released
50// major-version contract; do not reuse or reinterpret an assigned value.
51define_fact_tag_registry! {
52    AcceptedSchemaFingerprintMethod = 1;
53    AcceptedSchemaFingerprintHigh = 2;
54    AcceptedSchemaFingerprintLow = 3;
55    ExpectedFingerprintPrefix = 4;
56    ActualFingerprintPrefix = 5;
57    EntityTag = 6;
58    ExpectedEntityTag = 7;
59    ActualEntityTag = 8;
60    ConstraintId = 9;
61    FieldId = 10;
62    IndexId = 11;
63    RelationId = 12;
64    MutationOperation = 13;
65    RowOperation = 14;
66    BatchPosition = 15;
67    FirstBatchPosition = 16;
68    DuplicateBatchPosition = 17;
69    ClauseIndex = 18;
70    TermIndex = 19;
71    FirstTermIndex = 20;
72    DuplicateTermIndex = 21;
73    ProjectionIndex = 22;
74    GroupIndex = 23;
75    AggregateIndex = 24;
76    ArgumentIndex = 25;
77    BranchIndex = 26;
78    ComponentIndex = 27;
79    ParameterIndex = 28;
80    SourceSpanStart = 29;
81    SourceSpanEnd = 30;
82    Expected = 31;
83    Actual = 32;
84    Minimum = 33;
85    Maximum = 34;
86    Limit = 35;
87    ExpectedCount = 36;
88    ActualCount = 37;
89    ExpectedRevision = 38;
90    ActualRevision = 39;
91    CurrentRevision = 40;
92    RequestedRevision = 41;
93    ExpectedVersion = 42;
94    ActualVersion = 43;
95    CurrentVersion = 44;
96    RequestedVersion = 45;
97    ExpectedOffset = 46;
98    ActualOffset = 47;
99    ExpectedArity = 48;
100    ActualArity = 49;
101    ExpectedLength = 50;
102    ActualLength = 51;
103    ExpectedSlotCount = 52;
104    ActualSlotCount = 53;
105    RowLayout = 54;
106    HistoryFloor = 55;
107    CurrentLayout = 56;
108    PhysicalSlot = 57;
109    PhysicalGeneration = 58;
110    ExpectedMemoryId = 59;
111    ActualMemoryId = 60;
112    ConstraintKind = 61;
113    ConstraintContext = 62;
114    FieldKind = 63;
115    ValueKind = 64;
116    TypeFamily = 65;
117    FunctionKind = 66;
118    OperatorKind = 67;
119    AggregateKind = 68;
120    KeyNamespaceKind = 69;
121    ComponentKind = 70;
122    MismatchKind = 71;
123    DecodeReason = 72;
124    BudgetResource = 73;
125    MigrationPhase = 74;
126    DatabaseControlRecordKind = 75;
127    StateKind = 76;
128    PayloadComponent = 77;
129    ExpectedSignaturePrefix = 78;
130    ActualSignaturePrefix = 79;
131    FindingPosition = 80;
132    RootField = 81;
133    RecordMember = 82;
134    TupleElement = 83;
135    Newtype = 84;
136    EnumVariant = 85;
137    ListElement = 86;
138    SetElement = 87;
139    MapEntryKey = 88;
140    MapEntryValue = 89;
141}
142
143impl fmt::Debug for DiagnosticFactTag {
144    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
145        write!(f, "{}", self.raw())
146    }
147}
148
149/// Pack two accepted `u32` identities into one fact value without narrowing.
150#[must_use]
151pub const fn pack_u32_pair(high: u32, low: u32) -> u64 {
152    (high as u64) << 32 | low as u64
153}
154
155/// Recover the two accepted identities from one packed fact value.
156#[must_use]
157pub const fn unpack_u32_pair(value: u64) -> (u32, u32) {
158    let bytes = value.to_be_bytes();
159    (
160        u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]),
161        u32::from_be_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]),
162    )
163}
164
165#[cfg(test)]
166mod tests {
167    use super::{DiagnosticFactTag, ORDERED_FACT_TAGS, pack_u32_pair, unpack_u32_pair};
168
169    #[test]
170    fn fact_tag_registry_is_fixed_unique_and_contiguous() {
171        for (index, tag) in ORDERED_FACT_TAGS.iter().copied().enumerate() {
172            let expected = u8::try_from(index + 1).expect("fact-tag index fits u8");
173            assert_eq!(tag.raw(), expected);
174            assert_eq!(DiagnosticFactTag::known(expected), Some(tag));
175        }
176
177        assert_eq!(DiagnosticFactTag::known(0), None);
178        assert_eq!(DiagnosticFactTag::known(90), None);
179        assert_eq!(DiagnosticFactTag::known(u8::MAX), None);
180    }
181
182    #[test]
183    fn accepted_identity_pair_packing_is_exact() {
184        for pair in [
185            (0, 0),
186            (1, 2),
187            (u32::MAX, 0),
188            (0, u32::MAX),
189            (u32::MAX, u32::MAX),
190        ] {
191            assert_eq!(unpack_u32_pair(pack_u32_pair(pair.0, pair.1)), pair);
192        }
193    }
194}