Skip to main content

text_mirror/
lib.rs

1//! Converts binary and rich files into plain text and builds a replica
2//! text-mirror tree beside a provenance manifest.
3//!
4//! The manifest schema `text-mirror/manifest@1` is the public
5//! interface. The [`manifest`] module holds its serde types. See
6//! `docs/DESIGN.md` for the architecture.
7//!
8//! In-process converters cover text, Office and OpenDocument
9//! documents, and spreadsheets. Text-layer PDF and the media adapters
10//! run behind the sandbox [`runner`] in a jailed worker. A format no
11//! converter claims, or one whose adapter engine is not pinned yet, is
12//! inventoried by [`detect`] and recorded as `unsupported`, so the
13//! manifest always reports the true denominator.
14
15#![forbid(unsafe_code)]
16
17use std::path::{Path, PathBuf};
18
19use thiserror::Error as ThisError;
20
21pub mod bundle;
22pub mod convert;
23pub mod detect;
24pub mod hash;
25pub mod manifest;
26pub mod mirror;
27pub mod pipeline;
28pub mod report;
29#[cfg(unix)]
30pub mod runner;
31pub mod segments;
32pub mod walk;
33
34/// The typed adapter request and response bodies, re-exported so
35/// transcript rendering and tests reach them on every platform. On a
36/// unix target these are the same types the [`runner`] protocol uses.
37#[cfg(unix)]
38pub use runner::protocol::bodies as runner_bodies;
39
40/// Standalone copies of the transcript-shaped bodies for platforms
41/// without a jail backend, so transcript rendering still type-checks.
42#[cfg(not(unix))]
43pub mod runner_bodies {
44    use serde::{Deserialize, Serialize};
45
46    /// One transcribed speech segment.
47    #[derive(Debug, Clone, Serialize, Deserialize)]
48    pub struct AsrSegment {
49        /// Segment start in seconds.
50        pub start_seconds: f64,
51        /// Segment end in seconds.
52        pub end_seconds: f64,
53        /// One-based speaker index.
54        pub speaker: u32,
55        /// The transcribed text.
56        pub text: String,
57    }
58
59    /// One deduplicated on-screen text state.
60    #[derive(Debug, Clone, Serialize, Deserialize)]
61    pub struct ScreenState {
62        /// When this state first appeared, in seconds.
63        pub first_seen_seconds: f64,
64        /// The on-screen text.
65        pub text: String,
66    }
67}
68
69/// The BLAKE3 of the provider jail profile template on this platform,
70/// or `None` where no provider profile is measured.
71pub(crate) fn runner_template_blake3() -> Option<String> {
72    #[cfg(unix)]
73    {
74        runner::provider_profile_template_blake3()
75    }
76    #[cfg(not(unix))]
77    {
78        None
79    }
80}
81
82/// Errors returned by this library.
83///
84/// Conversion failures are not errors. They are recorded outcomes in
85/// the manifest. This type covers the cases where the pipeline itself
86/// cannot proceed.
87#[derive(Debug, ThisError)]
88pub enum Error {
89    /// A filesystem operation failed.
90    #[error("{op} {path}: {source}")]
91    Io {
92        /// The operation that failed, such as `open` or `read`.
93        op: &'static str,
94        /// The path the operation touched.
95        path: PathBuf,
96        /// The underlying error.
97        #[source]
98        source: std::io::Error,
99    },
100
101    /// A rules file failed to parse or validate.
102    #[error("rules file {name}: {message}")]
103    Rules {
104        /// The rules file name.
105        name: String,
106        /// What went wrong.
107        message: String,
108    },
109
110    /// A manifest shard failed to parse or validate.
111    #[error("manifest {path} line {line}: {message}")]
112    Manifest {
113        /// The shard path.
114        path: PathBuf,
115        /// The one-based line number.
116        line: usize,
117        /// What went wrong.
118        message: String,
119    },
120
121    /// A manifest record could not be encoded.
122    #[error("encode manifest record for {path}: {message}")]
123    Encode {
124        /// The shard path.
125        path: PathBuf,
126        /// What went wrong.
127        message: String,
128    },
129
130    /// A division name is not usable as a shard file name.
131    #[error("invalid division name {name:?}: use ASCII letters, digits, hyphen, underscore")]
132    Division {
133        /// The rejected name.
134        name: String,
135    },
136
137    /// The run layout is invalid.
138    #[error("invalid layout: {message}")]
139    Layout {
140        /// What is wrong with the layout.
141        message: String,
142    },
143
144    /// A bundle verb examined its input and refused it, naming every
145    /// offender. Refusal is an outcome, distinct from a verb that
146    /// could not run.
147    #[error("{verb} refused: {}", problems.join("; "))]
148    Refused {
149        /// The verb that refused.
150        verb: &'static str,
151        /// Every offending file, record, or field.
152        problems: Vec<String>,
153    },
154}
155
156impl Error {
157    pub(crate) fn io(op: &'static str, path: &Path, source: std::io::Error) -> Self {
158        Error::Io {
159            op,
160            path: path.to_path_buf(),
161            source,
162        }
163    }
164}
165
166/// The result type used across this library.
167pub type Result<T> = std::result::Result<T, Error>;