use std::fmt::Debug;
use std::fmt::Display;
use std::fmt::Formatter;
use std::fmt::Result;
use super::PathSegment;
#[inline]
fn is_simple_field(field: &str) -> bool {
let mut bytes = field.bytes();
let Some(first) = bytes.next() else {
return false;
};
(first.is_ascii_alphabetic() || first == b'_') && bytes.all(|byte| byte.is_ascii_alphanumeric() || byte == b'_')
}
fn append_quoted_field(output: &mut String, field: &str) {
const HEX: &[u8; 16] = b"0123456789abcdef";
output.push_str("[\"");
for character in field.chars() {
match character {
'"' => output.push_str("\\\""),
'\\' => output.push_str("\\\\"),
'\n' => output.push_str("\\n"),
'\r' => output.push_str("\\r"),
'\t' => output.push_str("\\t"),
'\u{0008}' => output.push_str("\\b"),
'\u{000c}' => output.push_str("\\f"),
control if (control as u32) < 0x20 => {
let byte = control as u8;
output.push_str("\\u00");
output.push(HEX[(byte >> 4) as usize] as char);
output.push(HEX[(byte & 0x0f) as usize] as char);
}
other => output.push(other),
}
}
output.push_str("\"]");
}
#[derive(Clone, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct ValidationPath {
segments: Vec<PathSegment>,
}
impl ValidationPath {
#[must_use]
#[inline]
pub const fn root() -> Self {
Self { segments: Vec::new() }
}
#[must_use]
#[inline]
pub fn as_segments(&self) -> &[PathSegment] {
&self.segments
}
#[must_use]
pub fn with_field(mut self, field: &'static str) -> Self {
self.segments.push(PathSegment::Field(field));
self
}
#[must_use]
pub fn with_index(mut self, index: usize) -> Self {
self.segments.push(PathSegment::Index(index));
self
}
#[must_use]
pub fn with_map_entry(mut self, index: usize) -> Self {
self.segments.push(PathSegment::MapEntry(index));
self
}
#[must_use]
pub fn with_map_key(mut self) -> Self {
self.segments.push(PathSegment::MapKey);
self
}
#[must_use]
pub fn with_map_value(mut self) -> Self {
self.segments.push(PathSegment::MapValue);
self
}
#[must_use]
pub fn concat(&self, relative: &Self) -> Self {
let mut segments = Vec::with_capacity(self.segments.len() + relative.segments.len());
segments.extend_from_slice(&self.segments);
segments.extend_from_slice(&relative.segments);
Self { segments }
}
#[must_use]
pub fn render(&self) -> String {
let mut rendered = String::new();
for segment in &self.segments {
match segment {
PathSegment::Field(field) => {
if !rendered.is_empty() {
rendered.push('.');
}
if is_simple_field(field) {
rendered.push_str(field);
} else {
append_quoted_field(&mut rendered, field);
}
}
PathSegment::Index(index) => {
rendered.push('[');
rendered.push_str(&index.to_string());
rendered.push(']');
}
PathSegment::MapEntry(index) => {
rendered.push_str(".<map-entry:");
rendered.push_str(&index.to_string());
rendered.push('>');
}
PathSegment::MapKey => rendered.push_str(".<map-key>"),
PathSegment::MapValue => rendered.push_str(".<map-value>"),
}
}
rendered
}
}
impl Debug for ValidationPath {
fn fmt(&self, formatter: &mut Formatter<'_>) -> Result {
formatter
.debug_struct("ValidationPath")
.field("segment_count", &self.segments.len())
.finish_non_exhaustive()
}
}
impl Display for ValidationPath {
fn fmt(&self, formatter: &mut Formatter<'_>) -> Result {
formatter.write_str("<validation-path>")
}
}