condor_grid/path.rs
1//! Validated discrete path value: ordered cells plus an algorithm-reported cost.
2//!
3//! [`Path`] is non-empty by construction. [`Path::from_steps`] records hop count;
4//! [`Path::from_steps_with_cost`] lets weighted algorithms preserve true traversal
5//! cost. It is the path payload in [`crate::SearchOutcome`], not a map-validating
6//! route checker.
7
8use crate::point::Point;
9
10/// Path construction failed because the step list was empty or otherwise invalid.
11#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
12#[non_exhaustive]
13pub enum PathBuildError {
14 /// `from_steps*` require at least one cell.
15 #[error("paths must contain at least one point")]
16 Empty,
17}
18
19/// Non-empty sequence of grid cells with an associated traversal cost.
20///
21/// Cost is hop-count (`from_steps`) or algorithm-supplied weighted cost
22/// (`from_steps_with_cost`). Paths do not validate adjacency on construction;
23/// callers use [`crate::Grid::path_is_walkable`] when geometry must be checked.
24#[derive(Debug, Clone, PartialEq, Eq)]
25pub struct Path {
26 steps: Vec<Point>,
27 cost: usize,
28}
29
30impl Path {
31 /// Builds a path with cost equal to `steps.len() - 1` (unit hop model).
32 ///
33 /// # Errors
34 ///
35 /// Returns [`PathBuildError::Empty`] when `steps` is empty.
36 pub fn from_steps(steps: Vec<Point>) -> Result<Self, PathBuildError> {
37 if steps.is_empty() {
38 return Err(PathBuildError::Empty);
39 }
40 let cost = steps.len().saturating_sub(1);
41 Ok(Self { steps, cost })
42 }
43
44 /// Builds a path with an explicit traversal cost (weighted solvers).
45 ///
46 /// # Errors
47 ///
48 /// Returns [`PathBuildError::Empty`] when `steps` is empty.
49 pub fn from_steps_with_cost(steps: Vec<Point>, cost: usize) -> Result<Self, PathBuildError> {
50 if steps.is_empty() {
51 return Err(PathBuildError::Empty);
52 }
53 Ok(Self { steps, cost })
54 }
55
56 /// Ordered cell sequence from start through goal (inclusive).
57 #[must_use]
58 pub fn steps(&self) -> &[Point] {
59 &self.steps
60 }
61
62 /// Number of cell steps (always ≥ 1 for constructed paths).
63 #[must_use]
64 pub fn len(&self) -> usize {
65 self.steps.len()
66 }
67
68 /// Always `false` for successfully constructed paths.
69 #[must_use]
70 pub const fn is_empty(&self) -> bool {
71 false
72 }
73
74 /// Hop count (`from_steps`) or algorithm-supplied traversal cost (`from_steps_with_cost`).
75 #[must_use]
76 pub fn cost(&self) -> usize {
77 self.cost
78 }
79
80 /// First step; always defined because paths are non-empty by construction.
81 #[must_use]
82 pub fn start(&self) -> Point {
83 self.steps[0]
84 }
85
86 /// Last step; always defined because paths are non-empty by construction.
87 #[must_use]
88 pub fn goal(&self) -> Point {
89 self.steps[self.steps.len() - 1]
90 }
91}