Skip to main content

pdf_xfa/
error.rs

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