grex_core/tree/error.rs
1//! Error taxonomy for the [`crate::tree`] walker.
2//!
3//! Errors carry `PathBuf` and `String` detail fields rather than boxing
4//! underlying loader or parser errors. Keeping leaky types out of the public
5//! surface means adding a new loader backend (IPC, in-memory, http) in a
6//! future slice stays non-breaking.
7
8use std::path::PathBuf;
9
10use thiserror::Error;
11
12use crate::git::GitError;
13
14/// Errors raised during a pack-tree walk.
15///
16/// Marked `#[non_exhaustive]` so later slices (credentials, submodules,
17/// partial walks) can add variants without breaking consumers.
18#[non_exhaustive]
19#[derive(Debug, Error)]
20pub enum TreeError {
21 /// The walker expected a `pack.yaml` at the given location but could not
22 /// find one (or its enclosing `.grex/` directory was missing).
23 #[error("pack manifest not found at `{0}`")]
24 ManifestNotFound(PathBuf),
25
26 /// The manifest file existed but could not be read from disk.
27 #[error("failed to read pack manifest: {0}")]
28 ManifestRead(String),
29
30 /// The manifest file was read but did not parse as a valid `pack.yaml`.
31 #[error("failed to parse pack manifest at `{path}`: {detail}")]
32 ManifestParse {
33 /// On-disk location of the manifest that failed to parse.
34 path: PathBuf,
35 /// Backend-provided failure detail.
36 detail: String,
37 },
38
39 /// A git operation (clone, fetch, checkout, …) failed while hydrating a
40 /// child pack. The underlying [`GitError`] is preserved in full.
41 #[error("git error during walk: {0}")]
42 Git(#[from] GitError),
43
44 /// A cycle was detected during the walk. `chain` lists the pack URLs (or
45 /// paths for the root) from the outermost pack down to the recurrence.
46 #[error("cycle detected in pack graph: {chain:?}")]
47 CycleDetected {
48 /// Ordered chain of pack identities that forms the cycle.
49 chain: Vec<String>,
50 },
51
52 /// A cloned child's `pack.yaml` declared a `name` that does not match
53 /// what the parent pack expected for that `children:` entry.
54 #[error("pack name `{got}` does not match expected `{expected}` for child at `{path}`")]
55 PackNameMismatch {
56 /// Name declared in the child's own manifest.
57 got: String,
58 /// Name the parent expected (derived from the child entry's
59 /// effective path).
60 expected: String,
61 /// On-disk location of the offending child.
62 path: PathBuf,
63 },
64
65 /// A `children[].path` (or URL-derived tail) violated the bare-name
66 /// rule. Surfaced by the walker BEFORE any clone of the offending
67 /// child fires so a malicious `path: ../escape` in a parent pack
68 /// cannot materialise a directory outside the pack root. This is a
69 /// security boundary, not a soft validation concern — see
70 /// `crates/grex-core/src/pack/validate/child_path.rs` for the shared
71 /// rejection logic.
72 #[error("pack child `{child_name}` has invalid path `{path}`: {reason}")]
73 ChildPathInvalid {
74 /// Label of the offending child (the explicit `path:` value, or
75 /// the URL-derived tail when `path:` is omitted).
76 child_name: String,
77 /// The rejected literal value.
78 path: String,
79 /// One-line explanation of which sub-rule failed.
80 reason: String,
81 },
82}