Skip to main content

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}