use super::Found;
use super::keys::is_metadata_key;
use super::suggest::{closest, found_text};
use crate::Diagnostic;
use crate::diagnostic::Problem;
use crate::model::borrowed::{MapRef, ValueRef};
use crate::model::value::{Path, Ref, RefKey, Seg};
use crate::schema::{Expected, Resolve, Schema};
#[derive(Default)]
pub struct Reader<'r> {
pub(crate) path: Vec<Step>,
pub(crate) value_only: bool,
pub(crate) resolver: Option<&'r mut dyn Resolve>,
pub problems: Vec<Diagnostic>,
pub refs: Vec<Found<Ref>>,
pub(crate) allowed: Vec<&'static str>,
pub(crate) expanding: usize,
}
impl std::fmt::Debug for Reader<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Reader")
.field("path", &self.path())
.field("value_only", &self.value_only)
.field("resolves", &self.resolver.is_some())
.field("problems", &self.problems)
.field("refs", &self.refs)
.finish_non_exhaustive()
}
}
pub(crate) enum Step {
Seg(Seg),
Field(&'static str),
}
impl<'r> Reader<'r> {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn with_resolver(resolver: &'r mut dyn Resolve) -> Self {
Self {
resolver: Some(resolver),
..Self::default()
}
}
pub fn resolve_ref(&mut self, r: &Ref) -> Option<RefKey> {
let result = match &mut self.resolver {
Some(res) => res.reference(r, &Self::built(&self.path)),
None => Ok(r.id.clone()),
};
result
.map_err(|e| self.problem_of(Problem::Unresolved, e.to_string()))
.ok()
}
pub fn take_ref(&mut self, r: Ref) -> Option<RefKey> {
if self.resolver.is_none() {
return Some(r.id);
}
self.resolve_ref(&r)
}
#[must_use]
pub fn keeps_found(&self) -> bool {
!self.value_only
}
#[must_use]
pub fn resolves(&self) -> bool {
self.resolver.is_some()
}
#[must_use]
pub fn path(&self) -> Path {
Self::built(&self.path)
}
fn built(steps: &[Step]) -> Path {
let mut p = Path::root();
for s in steps {
p.push(match s {
Step::Seg(s) => s.clone(),
Step::Field(k) => Seg::Key((*k).to_string()),
});
}
p
}
pub fn problem(&mut self, message: impl Into<String>) {
self.problem_of(Problem::Invalid, message);
}
pub fn problem_of(&mut self, problem: Problem, message: impl Into<String>) {
let found = Diagnostic::error(problem, message.into()).at_path(self.path());
self.problems.push(found);
}
pub fn expected(&mut self, what: Expected, found: &ValueRef<'_>) {
self.problem_of(
Problem::Expected,
format!("expected {what}, found {}", found_text(found)),
);
}
pub fn at<T>(&mut self, seg: Seg, f: impl FnOnce(&mut Self) -> T) -> T {
self.path.push(Step::Seg(seg));
let r = f(self);
self.path.pop();
r
}
fn in_field<T>(&mut self, key: &'static str, f: impl FnOnce(&mut Self) -> T) -> T {
self.path.push(Step::Field(key));
let r = f(self);
self.path.pop();
r
}
pub fn note_ref(&mut self, r: Ref) {
if self.value_only {
return;
}
let path = self.path();
self.refs.push(Found { value: r, path });
}
pub fn map<'v>(&mut self, v: &ValueRef<'v>) -> Option<MapRef<'v>> {
match v {
ValueRef::Map(m) => Some(*m),
other => {
self.expected(Expected::Object, other);
None
}
}
}
pub fn unknown_keys(&mut self, map: MapRef<'_>, known: &[&str]) {
for key in map.keys() {
if known.contains(&key) || is_metadata_key(key) || self.allowed.contains(&key) {
continue;
}
let hint = closest(key, known).map_or_else(
|| {
if known.is_empty() {
String::new()
} else {
format!(
" (expected {})",
known
.iter()
.map(|k| format!("`{k}`"))
.collect::<Vec<_>>()
.join(", ")
)
}
},
|k| format!(" — did you mean `{k}`?"),
);
let mut found = Diagnostic::error(
Problem::UnknownField,
format!("unknown field `{key}`{hint}"),
)
.at_path(self.path().key(key));
found.on_key = true;
self.problems.push(found);
}
}
pub fn unknown_name(&mut self, name: &str, known: &[&str]) {
let hint = match closest(name, known) {
Some(k) => format!(" — did you mean `{k}`?"),
None => format!(
" (expected {})",
known
.iter()
.map(|k| format!("`{k}`"))
.collect::<Vec<_>>()
.join(", ")
),
};
self.problem(format!("unknown `{name}`{hint}"));
}
pub fn field<T: Schema>(&mut self, map: MapRef<'_>, key: &'static str) -> Option<T> {
match map.get(key) {
Some(v) => self.at(Seg::Key(key.to_string()), |cx| T::read(v, cx)),
None => T::absent().or_else(|| {
self.problem_of(Problem::MissingField, format!("missing `{key}`"));
None
}),
}
}
pub fn field_or<T: Schema>(
&mut self,
map: MapRef<'_>,
key: &'static str,
default: impl FnOnce() -> T,
) -> Option<T> {
match map.get(key) {
Some(v) => self.at(Seg::Key(key.to_string()), |cx| T::read(v, cx)),
None => Some(default()),
}
}
pub fn field_with<W: crate::with::With<F>, F>(
&mut self,
map: MapRef<'_>,
key: &'static str,
) -> Option<F> {
match map.get(key) {
Some(v) => self.in_field(key, |cx| W::read(v, cx)),
None => W::absent().or_else(|| {
self.problem_of(Problem::MissingField, format!("missing `{key}`"));
None
}),
}
}
pub fn field_or_with<W: crate::with::With<F>, F>(
&mut self,
map: MapRef<'_>,
key: &'static str,
default: impl FnOnce() -> F,
) -> Option<F> {
match map.get(key) {
Some(v) => self.in_field(key, |cx| W::read(v, cx)),
None => Some(default()),
}
}
pub fn with_allowed<T>(&mut self, key: &'static str, f: impl FnOnce(&mut Self) -> T) -> T {
self.allowed.push(key);
let r = f(self);
self.allowed.pop();
r
}
pub fn with_allowed_all<T>(
&mut self,
keys: &[&'static str],
f: impl FnOnce(&mut Self) -> T,
) -> T {
let before = self.allowed.len();
self.allowed.extend_from_slice(keys);
let r = f(self);
self.allowed.truncate(before);
r
}
pub fn own_keys<T>(&mut self, f: impl FnOnce(&mut Self) -> T) -> T {
let saved = std::mem::take(&mut self.allowed);
let r = f(self);
self.allowed = saved;
r
}
}