Skip to main content

pdf_xfa/
error.rs

1//! Error types for the XFA engine.
2
3// Copyright (c) 2026 Innovation Trigger B.V.
4//
5// PDFluent is available under two licences, at your option: the GNU AGPLv3, or
6// the PDFluent Commercial Licence. See the LICENSE file in this repository --
7// that file travels with the copy you received, which a URL does not.
8use thiserror::Error;
9/// XfaError.
10
11#[derive(Debug, Error)]
12pub enum XfaError {
13    /// LoadFailed.
14    // ---- Existing variants (preserved for backwards compatibility) ----
15    #[error("failed to load PDF: {0}")]
16    LoadFailed(String),
17    /// PacketNotFound.
18    #[error("XFA packet not found: {0}")]
19    PacketNotFound(String),
20    /// Encrypted.
21    #[error("encrypted PDF: {0}")]
22    Encrypted(String),
23    /// XmlParse.
24    #[error("XML parse error: {0}")]
25    XmlParse(String),
26    /// FontError.
27    #[error("font error: {0}")]
28    FontError(String),
29    /// LayoutError.
30    #[error("layout error: {0}")]
31    LayoutError(String),
32    /// LayoutFailed.
33    #[error("layout failed: {0}")]
34    LayoutFailed(String),
35    /// ParseFailed.
36    #[error("XML parse failed: {0}")]
37    ParseFailed(String),
38    /// FormCalcError.
39    #[error("FormCalc error: {0}")]
40    FormCalcError(String),
41    /// Io.
42    #[error("IO error: {0}")]
43    Io(#[from] std::io::Error),
44
45    /// RenderingPolicyUnsupported. **D11.** The requested
46    /// [`crate::flatten::XfaRenderingPolicy`] is not available in the calling
47    /// context (e.g. a future/unknown policy, or a command such as `flatten`
48    /// that applies `SavedStateFaithful` only). Both `SavedStateFaithful` (the
49    /// default, production policy) and `FreshMergeExperimental` (experimental,
50    /// opt-in) are implemented as of D12; this variant exists so an unavailable
51    /// policy fails loudly rather than silently producing default output under
52    /// the wrong label.
53    #[error("XFA rendering policy not supported: {0}")]
54    RenderingPolicyUnsupported(String),
55
56    // ---- New structured variants (XFA-F9-01 #1120) ----
57    /// XFA packet extraction failed (e.g. missing /AcroForm, corrupt stream).
58    #[error("XFA extraction failed: {0}")]
59    ExtractionFailed(String),
60
61    /// Template XML could not be parsed.
62    #[error("Template parse error: {0}")]
63    TemplateParse(String),
64
65    /// Data binding from datasets to template failed.
66    #[error("Data binding failed: {0}")]
67    BindingFailed(String),
68    /// LayoutFailedAt.
69    /// LayoutFailedAt.
70
71    /// Layout failed at a specific pipeline stage.
72    ///
73    /// Use this variant when you can identify which stage (e.g. "paginate",
74    /// "split", "occur") caused the failure so callers can give better
75    /// diagnostics.
76    #[error("Layout failed at {stage}: {reason}")]
77    LayoutFailedAt {
78        /// Pipeline stage where layout failed.
79        stage: String,
80        /// Reason for the failure.
81        reason: String,
82    },
83
84    /// PDF content stream generation failed.
85    #[error("Render failed: {0}")]
86    RenderFailed(String),
87
88    /// Final PDF serialisation / flatten step failed.
89    #[error("Flatten failed: {0}")]
90    FlattenFailed(String),
91
92    /// Feature is intentionally unsupported by the non-interactive XFA engine.
93    #[error("unsupported feature: {0}")]
94    UnsupportedFeature(String),
95
96    /// XFA-JS-HOST-STUBS — A script reached a host capability that requires
97    /// genuine user / viewer interaction (e.g. `xfa.host.messageBox`,
98    /// `xfa.host.openList`, `xfa.signature.sign`). The flatten pipeline runs
99    /// non-interactively, so the call cannot be satisfied honestly. Inside the
100    /// sandbox the call is short-circuited to a safe default and counted in
101    /// [`crate::DynamicScriptOutcome::js_unsupported_host_calls`]; this error
102    /// variant exists for synchronous Rust API surfaces that want to surface
103    /// the gap to embedding callers rather than silently absorb it.
104    ///
105    /// The `capability` string is a stable, well-known identifier such as
106    /// `"xfa.host.messageBox"` and is suitable for inclusion in diagnostics.
107    #[error("unsupported host capability: {capability}")]
108    UnsupportedHostCapability {
109        /// Stable identifier of the host capability that was requested.
110        capability: String,
111    },
112
113    // ---- XfaSession field API (Phase 1 SDK foundation) ----
114    /// No XFA field with the given name exists in the current layout.
115    #[error("XFA field not found: {0}")]
116    FieldNotFound(String),
117
118    /// The field (or one of its ancestor containers) is `access="readOnly"`,
119    /// `"protected"`, or `"nonInteractive"` — value writes are rejected.
120    #[error("XFA field is read-only: {0}")]
121    FieldReadOnly(String),
122
123    /// The value is not assignable to the field (wrong kind, unknown choice
124    /// item, unknown radio on-value, …).
125    #[error("invalid value for XFA field {name}: {reason}")]
126    InvalidFieldValue {
127        /// Fully-qualified field name.
128        name: String,
129        /// Human-readable reason.
130        reason: String,
131    },
132
133    /// The datasets writeback could not locate or rewrite the XFA packet
134    /// stream in the PDF.
135    #[error("XFA datasets writeback failed: {0}")]
136    WritebackFailed(String),
137}
138/// Result.
139pub type Result<T> = std::result::Result<T, XfaError>;
140
141#[cfg(test)]
142mod tests {
143    use super::*;
144
145    #[test]
146    fn error_message_extraction_failed() {
147        let e = XfaError::ExtractionFailed("no /AcroForm key".to_string());
148        assert_eq!(format!("{e}"), "XFA extraction failed: no /AcroForm key");
149    }
150
151    #[test]
152    fn error_message_unsupported_host_capability() {
153        let e = XfaError::UnsupportedHostCapability {
154            capability: "xfa.host.messageBox".to_string(),
155        };
156        assert_eq!(
157            format!("{e}"),
158            "unsupported host capability: xfa.host.messageBox"
159        );
160    }
161
162    #[test]
163    fn error_message_layout_failed_at() {
164        let e = XfaError::LayoutFailedAt {
165            stage: "paginate".to_string(),
166            reason: "zero-height page area".to_string(),
167        };
168        assert_eq!(
169            format!("{e}"),
170            "Layout failed at paginate: zero-height page area"
171        );
172    }
173}