1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
//! Error type for the PDF form-fill engine.
/// Anything that can go wrong filling an official IRS PDF.
///
/// The **geometric read-back** failures (`Geometry`, `UnmappedField`) are the tax-safety net: a fill
/// that lands a value in the wrong cell — or writes any field the map did not authorize — FAILS
/// CLOSED (no PDF bytes are returned), so a mis-mapped form is never handed to a filer.
#[derive(Debug, thiserror::Error)]
pub enum FormsError {
/// The requested tax year has no bundled form set / map (this build ships TY2017, 2024 + 2025).
#[error("unsupported tax year {0}: this build bundles IRS forms for 2017, 2024 and 2025 only")]
UnsupportedYear(i32),
/// A field named by the map does not exist in the bundled PDF's AcroForm.
#[error("map references field {0:?} which is absent from the bundled PDF field set")]
MapFieldMissing(String),
/// More data rows than the form's page grid can hold on the paths that do not paginate.
#[error("{rows} rows exceed the {capacity}-row capacity of a single {part} page")]
Overflow {
/// The part being filled ("Part I" / "Part II").
part: &'static str,
/// The number of rows requested.
rows: usize,
/// The per-page row capacity from the map.
capacity: usize,
},
/// The geometric read-back found a written value in the WRONG column/row band — the map is
/// mis-aligned. Fails closed.
#[error("geometric read-back FAILED (mis-mapped cell): {0}")]
Geometry(String),
/// A field carries a value but the map never authorized writing it (a stray write). Fails closed.
#[error("read-back FAILED: unmapped field {0:?} was filled")]
UnmappedField(String),
/// A written value is longer than the cell's `/MaxLen` — the form declares a fixed-width (usually
/// **comb**) cell and the value does not fit. A PDF viewer would silently truncate it, or splay it
/// across the wrong comb teeth, so the fill fails closed instead: a truncated SSN on a filed return
/// is a wrong return.
#[error(
"read-back FAILED: {fqn:?} holds {len} characters but the cell's /MaxLen is {max_len}"
)]
CellOverflow {
/// The over-filled field.
fqn: String,
/// The cell's declared capacity.
max_len: usize,
/// The length actually written.
len: usize,
},
/// The bundled PDF's structure was not what the engine expects (missing AcroForm, bad Rect, …).
#[error("bundled PDF structure error: {0}")]
Structure(String),
/// Underlying lopdf parse/serialize error.
#[error("pdf error: {0}")]
Pdf(#[from] lopdf::Error),
/// A committed TOML map failed to parse.
#[error("map parse error: {0}")]
Map(#[from] toml::de::Error),
/// I/O error serializing the PDF.
#[error("io error: {0}")]
Io(#[from] std::io::Error),
}