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}