tfparser_core/exporter/mod.rs
1//! Parquet exporter — turns a [`Workspace`](crate::ir::Workspace) into
2//! `resources.parquet` (+ `workspace.manifest.json`) on disk per
3//! [20-parquet-exporter.md].
4//!
5//! Phase 3 closes M0 by shipping the **single-table** flat schema pinned in
6//! [10-data-model.md § 3] (24 columns, all non-null with `""` / empty-list
7//! as the "missing" sentinel). Future phases extend the output with
8//! `dependencies.parquet`, `components.parquet`, `modules.parquet`, etc. —
9//! same writer pattern, separate file, same atomic-rename guarantee.
10//!
11//! ## Trust boundary
12//!
13//! The exporter consumes only IR — `Workspace` values are already trusted
14//! (they crossed validation at discovery / loader / evaluator land). The
15//! one trust boundary the exporter polices is the *output path*: it
16//! refuses to overwrite existing files unless `--overwrite` is set, and
17//! writes atomically via `<file>.partial` → `rename` (per
18//! [99-key-decisions.md] D10).
19//!
20//! [20-parquet-exporter.md]: ../../../specs/20-parquet-exporter.md
21//! [10-data-model.md § 3]: ../../../specs/10-data-model.md
22
23pub mod json;
24mod manifest;
25mod schema;
26mod secondary;
27mod writer;
28
29use std::sync::Arc;
30
31pub use manifest::{Manifest, ManifestFile, write_manifest};
32pub use schema::{
33 PARSER_VERSION, SCHEMA_MAJOR, SCHEMA_MINOR, resources_schema, schema_field_names,
34};
35pub use secondary::{
36 components_field_names, components_schema, dependencies_field_names, dependencies_schema,
37 modules_field_names, modules_schema,
38};
39pub use writer::{
40 CompressionOpt, ExportOptions, ExportReport, ExportedFile, Exporter, ParquetExporter,
41 SecondaryTable,
42};
43
44/// Errors the exporter can raise.
45///
46/// Each variant carries the offending path when known, so the user can
47/// jump straight to it — see [20-parquet-exporter.md § 5].
48#[derive(Debug, thiserror::Error)]
49#[non_exhaustive]
50pub enum ExportError {
51 /// The output path exists and `--overwrite` was not set.
52 #[error("output exists and --overwrite not set: {0}")]
53 OutputExists(Arc<std::path::Path>),
54
55 /// Output directory does not exist.
56 #[error("output directory does not exist: {0}")]
57 OutDirMissing(Arc<std::path::Path>),
58
59 /// Output path exists but is not a directory.
60 #[error("output path is not a directory: {0}")]
61 OutDirNotDir(Arc<std::path::Path>),
62
63 /// I/O failure while writing.
64 #[error("i/o error at {path}: {source}")]
65 Io {
66 /// Path that triggered the error.
67 path: Arc<std::path::Path>,
68 /// Underlying I/O error.
69 #[source]
70 source: std::io::Error,
71 },
72
73 /// Arrow / Parquet writer raised.
74 #[error("parquet writer error at {path}: {source}")]
75 Parquet {
76 /// Target file.
77 path: Arc<std::path::Path>,
78 /// Underlying parquet error.
79 #[source]
80 source: parquet::errors::ParquetError,
81 },
82
83 /// Arrow batch construction raised.
84 #[error("arrow error at {path}: {source}")]
85 Arrow {
86 /// Target file.
87 path: Arc<std::path::Path>,
88 /// Underlying arrow error.
89 #[source]
90 source: arrow::error::ArrowError,
91 },
92
93 /// JSON manifest serialisation raised.
94 #[error("manifest serialisation error at {path}: {source}")]
95 Manifest {
96 /// Manifest path.
97 path: Arc<std::path::Path>,
98 /// Underlying serialisation error.
99 #[source]
100 source: serde_json::Error,
101 },
102}