use facet_core::Def;
use facet_reflect::{FieldPath, Partial, Span, VariantSelection};
use facet_solver::PathSegment;
use crate::{DeserializeError, SpanGuard};
#[derive(Debug, Clone)]
struct OpenSegment {
name: &'static str,
is_option: bool,
}
pub(crate) struct PathNavigator<'input, const BORROW: bool> {
wip: Option<Partial<'input, BORROW>>,
open_segments: Vec<OpenSegment>,
last_span: Span,
}
impl<'input, const BORROW: bool> PathNavigator<'input, BORROW> {
pub fn new(wip: Partial<'input, BORROW>, last_span: Span) -> Self {
Self {
wip: Some(wip),
open_segments: Vec::new(),
last_span,
}
}
pub fn set_span(&mut self, span: Span) {
self.last_span = span;
}
pub fn wip(&self) -> &Partial<'input, BORROW> {
self.wip.as_ref().expect("wip taken but not returned")
}
pub fn take_wip(&mut self) -> Partial<'input, BORROW> {
self.wip.take().expect("wip taken but not returned")
}
pub fn return_wip(&mut self, wip: Partial<'input, BORROW>) {
assert!(self.wip.is_none(), "wip returned but was not taken");
self.wip = Some(wip);
}
pub fn into_wip(mut self) -> Partial<'input, BORROW> {
self.wip.take().expect("wip taken but not returned")
}
pub fn navigate_to(
&mut self,
target: &FieldPath,
variant_selections: &[VariantSelection],
) -> Result<NavigateResult, DeserializeError> {
let _guard = SpanGuard::new(self.last_span);
let target_fields: Vec<&'static str> = target
.segments()
.iter()
.filter_map(|s| match s {
PathSegment::Field(name) => Some(*name),
PathSegment::Variant(_, _) => None,
})
.collect();
let trailing_variant = match target.segments().last() {
Some(PathSegment::Variant(_, name)) => Some(*name),
_ => None,
};
let common_len = self
.open_segments
.iter()
.zip(target_fields.iter())
.take_while(|(seg, field_name)| seg.name == **field_name)
.count();
self.close_to(common_len)?;
let segments_to_open = &target_fields[common_len..];
let (intermediate, final_segment) = match segments_to_open {
[] => (&[][..], None),
[.., last] => (&segments_to_open[..segments_to_open.len() - 1], Some(*last)),
};
for &segment in intermediate {
self.open_segment(segment, variant_selections)?;
}
let final_is_option = if let Some(segment) = final_segment {
let mut wip = self.take_wip();
wip = wip.begin_field(segment)?;
let is_option = matches!(wip.shape().def, Def::Option(_));
self.return_wip(wip);
is_option
} else {
false
};
Ok(NavigateResult {
_common_len: common_len,
final_segment,
final_is_option,
trailing_variant,
})
}
fn open_segment(
&mut self,
name: &'static str,
variant_selections: &[VariantSelection],
) -> Result<(), DeserializeError> {
let _guard = SpanGuard::new(self.last_span);
let mut wip = self.take_wip();
wip = wip.begin_field(name)?;
let is_option = matches!(wip.shape().def, Def::Option(_));
if is_option {
wip = wip.begin_some()?;
}
let current_path: Vec<&str> = self
.open_segments
.iter()
.map(|seg| seg.name)
.chain(core::iter::once(name))
.collect();
for vs in variant_selections {
let vs_fields: Vec<&str> = vs
.path
.segments()
.iter()
.filter_map(|s| match s {
PathSegment::Field(f) => Some(*f),
PathSegment::Variant(_, _) => None,
})
.collect();
trace!(
"open_segment: checking variant selection: current_path={:?}, vs_fields={:?}, variant={}",
current_path, vs_fields, vs.variant_name
);
if current_path == vs_fields {
trace!(
"open_segment: selecting variant '{}' at path {:?}",
vs.variant_name, current_path
);
wip = wip.select_variant_named(vs.variant_name)?;
break;
}
}
self.return_wip(wip);
self.open_segments.push(OpenSegment { name, is_option });
Ok(())
}
pub fn close_final(&mut self, _is_option: bool) -> Result<(), DeserializeError> {
let _guard = SpanGuard::new(self.last_span);
let mut wip = self.take_wip();
wip = wip.end()?;
self.return_wip(wip);
Ok(())
}
pub fn close_to(&mut self, target_len: usize) -> Result<(), DeserializeError> {
let _guard = SpanGuard::new(self.last_span);
while self.open_segments.len() > target_len {
let seg = self.open_segments.pop().unwrap();
let mut wip = self.take_wip();
if seg.is_option {
wip = wip.end()?;
}
wip = wip.end()?;
self.return_wip(wip);
}
Ok(())
}
pub fn close_all(&mut self) -> Result<(), DeserializeError> {
self.close_to(0)
}
pub fn keep_final_open(&mut self, nav_result: &NavigateResult) {
if let Some(final_seg) = nav_result.final_segment {
self.open_segments.push(OpenSegment {
name: final_seg,
is_option: nav_result.final_is_option,
});
}
}
}
pub(crate) struct NavigateResult {
pub _common_len: usize,
pub final_segment: Option<&'static str>,
pub final_is_option: bool,
pub trailing_variant: Option<&'static str>,
}