Skip to main content

rustyfi_loader/
error.rs

1use std::path::PathBuf;
2
3use rustyfi_syntax::{ParseFileError, RustyfiVersion};
4
5/// Everything that can go wrong while loading a multi-file SATySFi program.
6#[derive(Debug, thiserror::Error)]
7pub enum LoadError {
8    /// Could not read a source file from disk.
9    #[error("{path}: {source}")]
10    Io {
11        path: PathBuf,
12        #[source]
13        source: std::io::Error,
14    },
15
16    /// A file failed to parse (lex or grammar error). `source` carries the
17    /// original [`ParseFileError`] (span + message) unchanged.
18    #[error("{path}: {source}")]
19    Parse {
20        path: PathBuf,
21        #[source]
22        source: ParseFileError,
23    },
24
25    /// `@require: name` could not be resolved to any file on disk.
26    #[error(
27        "cannot resolve `@require: {name}`; searched: {}",
28        format_searched(.searched)
29    )]
30    UnresolvedRequire {
31        name: String,
32        searched: Vec<PathBuf>,
33    },
34
35    /// `@import: name` could not be resolved to any file on disk.
36    #[error(
37        "cannot resolve `@import: {name}` from {}; searched: {}",
38        .from.display(),
39        format_searched(.searched)
40    )]
41    UnresolvedImport {
42        name: String,
43        from: PathBuf,
44        searched: Vec<PathBuf>,
45    },
46
47    /// The dependency graph contains a cycle; `chain` names the files
48    /// involved, in traversal order, with the first file repeated at the end
49    /// to make the loop explicit (e.g. `[a, b, a]`).
50    #[error("dependency cycle detected: {}", format_chain(.chain))]
51    Cycle { chain: Vec<PathBuf> },
52
53    /// A file reached via `@require:`/`@import:` is a document (has a body)
54    /// rather than a library.
55    #[error(
56        "{path}: required/imported file must be a library (no `in ...` body), found a document"
57    )]
58    DocumentAsDependency { path: PathBuf },
59
60    /// The entry file is a library (no body) rather than a document.
61    #[error("{path}: entry file must be a document (with an `in ...` body), found a library")]
62    LibraryAsEntry { path: PathBuf },
63
64    /// `opts.version` names a SATySFi version this port does not implement
65    /// yet (checked before any file is even read).
66    #[error(
67        "SATySFi {requested} documents are not supported yet; supported: {}",
68        format_versions(.supported)
69    )]
70    UnsupportedVersion {
71        requested: RustyfiVersion,
72        supported: Vec<RustyfiVersion>,
73    },
74
75    /// `LoadMode::Envelopes` was requested for a version with no `use`
76    /// headers — the rejected combination. Checked before any file is read.
77    #[error(
78        "SATySFi {version} has no `use` headers; `Envelopes` mode requires 0.1 \
79         (drop --deps, or pass --lang 0.1)"
80    )]
81    InvalidModeVersion { version: RustyfiVersion },
82
83    /// `use package Mod` with no deps config to resolve `Mod` against
84    /// (upstream's used_as map is empty).
85    #[error(
86        "{from}: cannot resolve `use package {module}` — no pre-resolved \
87         dependency graph; pass --deps <rustyfi-deps.yaml>"
88    )]
89    PackageDependencyUnresolved { module: String, from: PathBuf },
90
91    /// Bare `use Mod` at document/open level (upstream `CannotUseHeaderUse`):
92    /// a document cannot reach into a package's internals by module name.
93    /// Permanent (not a stub): the closed resolver handles bare `use` only
94    /// *inside* envelope source trees.
95    #[error(
96        "{from}: bare `use {module}` is only allowed between files inside one \
97         package; a document must say `use package {module}` or `use {module} \
98         of \"<path>\"`"
99    )]
100    BareUseOutsidePackage { module: String, from: PathBuf },
101
102    /// `` use … of `relpath` `` matched no candidate file on disk.
103    #[error(
104        "cannot resolve `use … of `{relpath}`` from {}; searched: {}",
105        .from.display(),
106        format_searched(.searched)
107    )]
108    UnresolvedUseOf {
109        relpath: String,
110        from: PathBuf,
111        searched: Vec<PathBuf>,
112    },
113
114    /// A `use`-family header under Legacy mode (the mode that resolves
115    /// `@require:`/`@import:`). Names the fix.
116    #[error(
117        "{from}: `{header}` requires Envelopes mode (pass --deps, or let a \
118         `use` header pin it); this load ran in Legacy (@require/@import) mode"
119    )]
120    EnvelopeHeaderUnderLegacy { header: String, from: PathBuf },
121
122    /// A Legacy (`@require:`/`@import:`) header under Envelopes mode. Names
123    /// the fix.
124    #[error(
125        "{from}: `{header}` is a Legacy (@require/@import) header; Envelopes \
126         mode resolves `use package` / `use … of` headers only"
127    )]
128    LegacyHeaderUnderEnvelopes { header: String, from: PathBuf },
129
130    /// The `--deps` file (`rustyfi-deps.yaml`) could not be read.
131    /// Upstream `DepsConfigNotFound`, `depsConfig.ml:40-45`.
132    #[error("{path}: cannot read rustyfi-deps.yaml: {source}")]
133    DepsConfigNotFound {
134        path: PathBuf,
135        #[source]
136        source: std::io::Error,
137    },
138
139    /// `rustyfi-deps.yaml` failed to decode or validate — either a
140    /// YAML/shape error from `serde_yaml` or one of the two non-structural
141    /// checks (`path` must be absolute; `used_as` must be an uppercased
142    /// identifier). Upstream `DepsConfigError` wrapping `YamlError`
143    /// (`depsConfig.ml:46-47`, `yamlDecoder.ml`); `message` carries a
144    /// dotted-path context string in the same spirit as upstream's
145    /// `show_yaml_context` (wording is this port's own).
146    #[error("{path}: invalid rustyfi-deps.yaml: {message}")]
147    DepsConfigDecode { path: PathBuf, message: String },
148
149    /// A `rustyfi-envelope.yaml` could not be read. Upstream
150    /// `EnvelopeConfigNotFound`, `envelopeConfig.ml:142-147`.
151    #[error("{path}: cannot read rustyfi-envelope.yaml: {source}")]
152    EnvelopeConfigNotFound {
153        path: PathBuf,
154        #[source]
155        source: std::io::Error,
156    },
157
158    /// A `rustyfi-envelope.yaml` failed to decode or validate.
159    /// Upstream `EnvelopeConfigError`. Covers the `library`/`font` branch
160    /// check, `opentype_single`/`opentype_collection` branch check, relative
161    /// `path`, lowercased font `name`, and the 18 `markdown_conversion`
162    /// command/identifier shapes.
163    #[error("{path}: invalid rustyfi-envelope.yaml: {message}")]
164    EnvelopeConfigDecode { path: PathBuf, message: String },
165
166    /// A `source_directories` entry of an envelope could not be
167    /// listed. Upstream `CannotReadDirectory`, `envelopeReader.ml:15-16`.
168    #[error("{path}: cannot list envelope source directory: {source}")]
169    CannotReadDirectory {
170        path: PathBuf,
171        #[source]
172        source: std::io::Error,
173    },
174
175    /// Two deps-config envelopes share a name. Upstream
176    /// `EnvelopeNameConflict`, `closedEnvelopeDependencyResolver.ml:50-51`.
177    #[error("rustyfi-deps.yaml: two envelopes are named `{name}`")]
178    EnvelopeNameConflict { name: String },
179
180    /// An envelope depends on a name absent from the deps config's
181    /// envelope set. Upstream `DependencyOnUnknownEnvelope`,
182    /// `closedEnvelopeDependencyResolver.ml:71-76`.
183    #[error("envelope `{depending}` depends on unknown envelope `{depended}`")]
184    DependencyOnUnknownEnvelope { depending: String, depended: String },
185
186    /// A cycle in the envelope dependency graph. Upstream
187    /// `CyclicEnvelopeDependency`; `chain` is names (not paths), the first
188    /// element repeated last, matching [`LoadError::Cycle`]'s shape.
189    #[error("envelope dependency cycle: {}", .chain.join(" -> "))]
190    CyclicEnvelopeDependency { chain: Vec<String> },
191
192    /// Two source files in one envelope declare the same module
193    /// name. Upstream `FileModuleNameConflict`,
194    /// `closedFileDependencyResolver.ml:20-22`.
195    #[error(
196        "envelope module `{module}` is declared by both {} and {}",
197        .prev.display(),
198        .path.display()
199    )]
200    FileModuleNameConflict {
201        module: String,
202        prev: PathBuf,
203        path: PathBuf,
204    },
205
206    /// A bare `use M` inside an envelope names no sibling module.
207    /// Upstream `FileModuleNotFound`, `closedFileDependencyResolver.ml:37-41`.
208    #[error("{from}: `use {module}` names no module in this envelope")]
209    FileModuleNotFound { module: String, from: PathBuf },
210
211    /// A `use … of` header inside an envelope source tree. Upstream
212    /// `CannotUseHeaderUseOf`, `closedFileDependencyResolver.ml:51-52`.
213    #[error(
214        "{from}: `use {module} of …` is not allowed inside a package; package \
215         files address siblings by bare `use <Module>`"
216    )]
217    UseOfInsidePackage { module: String, from: PathBuf },
218
219    /// A `use package M` header whose head matches no `used_as` alias
220    /// in the supplied deps config. Upstream `UnknownPackageDependency`
221    /// (typecheck-side there, load-side here — this port's loader is the only
222    /// header consumer).
223    #[error(
224        "{from}: `use package {module}` does not match any dependency alias \
225         (`used_as`) in the supplied rustyfi-deps.yaml"
226    )]
227    UnknownPackageDependency { module: String, from: PathBuf },
228}
229
230fn format_versions(versions: &[RustyfiVersion]) -> String {
231    versions
232        .iter()
233        .map(|v| v.to_string())
234        .collect::<Vec<_>>()
235        .join(", ")
236}
237
238fn format_searched(searched: &[PathBuf]) -> String {
239    if searched.is_empty() {
240        "(no candidates; is `lib_root` configured?)".to_string()
241    } else {
242        searched
243            .iter()
244            .map(|p| p.display().to_string())
245            .collect::<Vec<_>>()
246            .join(", ")
247    }
248}
249
250fn format_chain(chain: &[PathBuf]) -> String {
251    chain
252        .iter()
253        .map(|p| p.display().to_string())
254        .collect::<Vec<_>>()
255        .join(" -> ")
256}