use crate::error::FieldError;
use crate::node::Node;
use crate::value::Value;
use std::any::Any;
use std::collections::HashMap;
use std::sync::Arc;
#[derive(Clone, Debug, PartialEq)]
pub enum PathPart {
Key(Arc<str>),
Index(usize),
}
impl PathPart {
pub fn as_key(&self) -> String {
match self {
PathPart::Key(k) => k.to_string(),
PathPart::Index(i) => i.to_string(),
}
}
fn write_to(&self, out: &mut String) {
match self {
PathPart::Key(k) => out.push_str(k),
PathPart::Index(i) => out.push_str(&i.to_string()),
}
}
}
#[derive(Default)]
pub struct Context {
pub custom: HashMap<String, Box<dyn Any + Send + Sync>>,
pub refs: HashMap<String, Arc<Node>>,
pub err: Vec<FieldError>,
pub(crate) terse: bool,
}
impl Context {
pub fn new() -> Context {
Context::default()
}
pub fn get<T: 'static>(&self, key: &str) -> Option<&T> {
self.custom.get(key).and_then(|b| b.downcast_ref::<T>())
}
pub fn set<T: Any + Send + Sync>(&mut self, key: &str, value: T) {
self.custom.insert(key.to_string(), Box::new(value));
}
}
pub struct State<'a> {
pub path_arr: &'a [PathPart],
pub key: &'a str,
pub value: &'a Value,
pub node: &'a Node,
pub parent_is_array: bool,
pub is_match: bool,
pub ctx: &'a mut Context,
pub absent: bool,
pub check_name: &'a str,
pub(crate) defs: &'a HashMap<String, Arc<Node>>,
}
impl<'a> State<'a> {
pub fn path_str(&self) -> String {
join_path(self.path_arr)
}
pub fn path_keys(&self) -> Vec<String> {
self.path_arr.iter().map(|p| p.as_key()).collect()
}
}
pub fn join_path(path: &[PathPart]) -> String {
let mut out = String::new();
for p in path {
if matches!(p, PathPart::Key(k) if k.is_empty()) {
continue;
}
if !out.is_empty() {
out.push('.');
}
p.write_to(&mut out);
}
out
}
#[derive(Clone, Debug)]
pub enum UpdateErr {
Text(String),
Field(Box<FieldError>),
Fields(Vec<FieldError>),
}
#[derive(Default)]
pub struct Update {
pub done: bool,
pub why: Option<String>,
pub mark: i64,
pub err: Option<UpdateErr>,
pub val: Option<Value>,
pub node: Option<Arc<Node>>,
pub fatal: bool,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn context_custom_is_typed() {
let mut c = Context::new();
c.set("n", 3u32);
assert_eq!(c.get::<u32>("n"), Some(&3));
assert_eq!(c.get::<String>("n"), None);
assert_eq!(c.get::<u32>("m"), None);
}
#[test]
fn paths_join_and_render() {
let k = |s: &str| PathPart::Key(s.into());
assert_eq!(
join_path(&[k("a"), k(""), k("b"), PathPart::Index(1)]),
"a.b.1"
);
assert_eq!(join_path(&[]), "");
assert_eq!(PathPart::Key("k".into()).as_key(), "k");
assert_eq!(PathPart::Index(2).as_key(), "2");
let mut ctx = Context::new();
let node = Node::default();
let path = [k("a")];
let defs = HashMap::new();
let s = State {
path_arr: &path,
key: "a",
value: &Value::Null,
node: &node,
parent_is_array: false,
is_match: false,
ctx: &mut ctx,
absent: false,
check_name: "",
defs: &defs,
};
assert_eq!(s.path_str(), "a");
assert_eq!(s.path_keys(), vec!["a".to_string()]);
let u = Update::default();
assert!(!u.done && u.val.is_none());
}
}