use std::fmt;
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum Step {
Key(String),
Index(usize),
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
pub struct FieldPath(Vec<Step>);
impl FieldPath {
#[must_use]
pub fn root() -> Self {
Self::default()
}
#[must_use]
pub fn key(mut self, key: impl Into<String>) -> Self {
self.0.push(Step::Key(key.into()));
self
}
#[must_use]
pub fn index(mut self, index: usize) -> Self {
self.0.push(Step::Index(index));
self
}
#[must_use]
pub fn steps(&self) -> &[Step] {
&self.0
}
#[cfg(feature = "validate")]
pub(crate) fn starts_with(&self, prefix: &Self) -> bool {
self.0.starts_with(&prefix.0)
}
#[cfg(feature = "validate")]
pub(crate) fn without(&self, positions: &[usize]) -> Self {
Self(
self.0
.iter()
.enumerate()
.filter(|(position, _)| !positions.contains(position))
.map(|(_, step)| step.clone())
.collect(),
)
}
}
impl fmt::Display for FieldPath {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
for (position, step) in self.0.iter().enumerate() {
match step {
Step::Key(key) => {
if position > 0 {
formatter.write_str(".")?;
}
formatter.write_str(key)?;
}
Step::Index(index) => write!(formatter, "[{index}]")?,
}
}
Ok(())
}
}
#[cfg(feature = "validate")]
impl From<&garde::error::Path> for FieldPath {
fn from(path: &garde::error::Path) -> Self {
use garde::error::Kind;
let mut steps = Vec::new();
for (kind, component) in path.__iter().rev() {
match kind {
Kind::Key => steps.push(Step::Key(component.to_string())),
Kind::Index => steps.push(
component
.parse::<usize>()
.map_or_else(|_| Step::Key(component.to_string()), Step::Index),
),
Kind::None => {}
}
}
Self(steps)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub struct Place {
pub line: usize,
pub column: usize,
}
impl Place {
pub(crate) const fn new(line: usize, column: usize) -> Self {
Self { line, column }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_path_prints_as_garde_prints_a_field() {
let path = FieldPath::root()
.key("files")
.key("docs/**/*.md")
.key("vocab")
.index(1);
assert_eq!(path.to_string(), "files.docs/**/*.md.vocab[1]");
assert_eq!(FieldPath::root().index(0).key("a").to_string(), "[0].a");
assert_eq!(FieldPath::root().to_string(), "");
}
#[cfg(feature = "validate")]
#[test]
fn a_garde_path_converts_from_its_parts() {
let path = garde::error::Path::new("files")
.join("docs/**/*.md")
.join("vocab")
.join(0_usize);
assert_eq!(
FieldPath::from(&path).steps(),
[
Step::Key("files".to_owned()),
Step::Key("docs/**/*.md".to_owned()),
Step::Key("vocab".to_owned()),
Step::Index(0),
]
);
assert_eq!(path.to_string(), "files.docs/**/*.md.vocab[0]");
}
#[cfg(feature = "validate")]
#[test]
fn a_numeric_key_stays_a_key_and_a_keyless_part_is_dropped() {
let path = garde::error::Path::new("files")
.join("2")
.join(garde::error::NoKey::default())
.join("label");
assert_eq!(
FieldPath::from(&path).steps(),
[
Step::Key("files".to_owned()),
Step::Key("2".to_owned()),
Step::Key("label".to_owned()),
]
);
}
}