Skip to main content

appcore_filemaker/
error.rs

1// =============================================================================
2//        #######
3//     ###       ###     F: error.rs
4//    ##   ## ##   ##    P: AppCore-Runtime
5//         ## ##
6//                       C: 2026/08/30 05:00:00 by dnettoRaw
7//    ##   ## ##   ##    U: 2026/08/30 05:00:00 by dnettoRaw
8//      ###########      S: 1.0.2-rc
9// =============================================================================
10
11use std::fmt;
12
13use thiserror::Error;
14
15/// Stable machine-readable failure code.
16#[derive(Clone, Copy, Debug, Eq, PartialEq)]
17pub enum ErrorCode {
18    /// Source is not syntactically valid.
19    SchemaSyntax,
20    /// Source schema version is absent or unsupported.
21    SchemaVersion,
22    /// Source contains an invalid field or value.
23    SchemaField,
24    /// A typed data value or binding is invalid.
25    DataType,
26    /// Computed data contains a dependency cycle.
27    DataCycle,
28    /// An asset reference violates the resolver sandbox.
29    AssetSandbox,
30    /// An asset is missing or invalid.
31    AssetInvalid,
32    /// A required font or glyph is unavailable.
33    FontMissing,
34    /// Geometry overflowed or violates an invariant.
35    GeometryInvalid,
36    /// Layout constraints cannot be resolved.
37    LayoutInvalid,
38    /// Collision/reflow did not converge within the configured bound.
39    LayoutNonConvergent,
40    /// A patch is malformed or targets an absent node.
41    PatchInvalid,
42    /// A patch attempted to mutate a locked node.
43    PatchLocked,
44    /// The requested exporter does not support a required feature.
45    ExportUnsupported,
46    /// The output writer failed.
47    ExportWrite,
48    /// A configured resource budget was exceeded.
49    LimitExceeded,
50    /// Cooperative cancellation was requested.
51    Cancelled,
52    /// Validation or preflight rejected the requested operation.
53    Validation,
54}
55
56impl ErrorCode {
57    /// Returns the stable `FM-*` representation.
58    #[must_use]
59    pub const fn as_str(self) -> &'static str {
60        match self {
61            Self::SchemaSyntax => "FM-SCHEMA-SYNTAX",
62            Self::SchemaVersion => "FM-SCHEMA-VERSION",
63            Self::SchemaField => "FM-SCHEMA-FIELD",
64            Self::DataType => "FM-DATA-TYPE",
65            Self::DataCycle => "FM-DATA-CYCLE",
66            Self::AssetSandbox => "FM-ASSET-SANDBOX",
67            Self::AssetInvalid => "FM-ASSET-INVALID",
68            Self::FontMissing => "FM-FONT-MISSING",
69            Self::GeometryInvalid => "FM-GEOM-INVALID",
70            Self::LayoutInvalid => "FM-LAYOUT-INVALID",
71            Self::LayoutNonConvergent => "FM-LAYOUT-NON-CONVERGENT",
72            Self::PatchInvalid => "FM-PATCH-INVALID",
73            Self::PatchLocked => "FM-PATCH-LOCKED",
74            Self::ExportUnsupported => "FM-EXPORT-UNSUPPORTED",
75            Self::ExportWrite => "FM-EXPORT-WRITE",
76            Self::LimitExceeded => "FM-LIMIT-EXCEEDED",
77            Self::Cancelled => "FM-CANCELLED",
78            Self::Validation => "FM-VALIDATION",
79        }
80    }
81}
82
83/// Typed compiler error with stable code and bounded context.
84#[derive(Debug, Error)]
85#[error("{code}: {message}")]
86pub struct FileMakerError {
87    code: CodeDisplay,
88    message: String,
89    source_path: Option<String>,
90}
91
92impl FileMakerError {
93    /// Creates an error while bounding user-controlled diagnostic text.
94    #[must_use]
95    pub fn new(code: ErrorCode, message: impl Into<String>) -> Self {
96        let mut message = message.into();
97        message.truncate(1_024);
98        Self {
99            code: CodeDisplay(code),
100            message,
101            source_path: None,
102        }
103    }
104
105    /// Attaches a bounded logical source path.
106    #[must_use]
107    pub fn at(mut self, source_path: impl Into<String>) -> Self {
108        let mut source_path = source_path.into();
109        source_path.truncate(512);
110        self.source_path = Some(source_path);
111        self
112    }
113
114    /// Returns the stable code.
115    #[must_use]
116    pub const fn code(&self) -> ErrorCode {
117        self.code.0
118    }
119
120    /// Returns the bounded human-readable diagnostic.
121    #[must_use]
122    pub fn message(&self) -> &str {
123        &self.message
124    }
125
126    /// Returns the logical input path, when available.
127    #[must_use]
128    pub fn source_path(&self) -> Option<&str> {
129        self.source_path.as_deref()
130    }
131}
132
133#[derive(Debug)]
134struct CodeDisplay(ErrorCode);
135
136impl fmt::Display for CodeDisplay {
137    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
138        formatter.write_str(self.0.as_str())
139    }
140}
141
142/// Result returned by `FileMaker` compiler operations.
143pub type Result<T> = std::result::Result<T, FileMakerError>;